windows message manager

来源:互联网 发布:批量导入数据到excel 编辑:程序博客网 时间:2024/05/22 07:54

SendMessage

窗口过程函数的调用有两个入口,一个是自己的线程给自己窗口发通知,这样直接调用内部函数进行调用,使用IntCallMessageProc来调用函数,另一种方式是转换用户消息为内核消息,调用NtUserMessageCall来传递消息。

LRESULT WINAPI SendMessageW(HWND Wnd,      UINT Msg,      WPARAM wParam,      LPARAM lParam){  MSG UMMsg, KMMsg;  LRESULT Result;  PWND Window;  PTHREADINFO ti = GetW32ThreadInfo();  if ( Msg & ~WM_MAXIMUM )  {     SetLastError( ERROR_INVALID_PARAMETER );     return 0;  }  if (Wnd != HWND_TOPMOST && Wnd != HWND_BROADCAST && (Msg < WM_DDE_FIRST || Msg > WM_DDE_LAST))  //进行前期参数判断,要求窗体句柄不能超过范围,消息号也要在正常的范围中。  {      Window = ValidateHwnd(Wnd);                        //根据窗体句柄获取具体窗体      if ( Window != NULL &&           Window->head.pti == ti &&          !ISITHOOKED(WH_CALLWNDPROC) &&          !ISITHOOKED(WH_CALLWNDPROCRET) &&          !(Window->state & WNDS_SERVERSIDEWINDOWPROC) )      {          /* NOTE: We can directly send messages to the window procedure                   if *all* the following conditions are met:                   * Window belongs to calling thread                   * The calling thread is not being hooked for CallWndProc                   * Not calling a server side proc:                     Desktop, Switch, ScrollBar, Menu, IconTitle, or hWndMessage           */          return IntCallMessageProc(Window, Wnd, Msg, wParam, lParam, FALSE);      }  }  UMMsg.hwnd = Wnd;  UMMsg.message = Msg;  UMMsg.wParam = wParam;  UMMsg.lParam = lParam;  if (! MsgiUMToKMMessage(&UMMsg, &KMMsg, FALSE))  {     return FALSE;  }  Result = NtUserMessageCall( Wnd,                              KMMsg.message,                              KMMsg.wParam,                              KMMsg.lParam,                             (ULONG_PTR)&Result,                              FNID_SENDMESSAGE,                              FALSE);  MsgiUMToKMCleanup(&UMMsg, &KMMsg);  return Result;}

函数提取出windows 的消息处理例程
另外一种,往其他进程或线程传递消息。我们使用的是NtUserMessageCall,在调用之前,我们转换用户信息到内核信息,就是进行数据拷贝。
NtUserMessageCall中有个switch ,我们传递的参数是FNID_SENDMESSAGE,指定我们是发送消息。于是乎我们进入函数中,可以看到如下,我们调用win32k的内部发送函数,然后记录调用的结果,返回给用户。

    case FNID_SENDMESSAGE:        {            Ret = co_IntDoSendMessage(hWnd, Msg, wParam, lParam, 0);            if (ResultInfo)            {                _SEH2_TRY                {                    ProbeForWrite((PVOID)ResultInfo, sizeof(ULONG_PTR), 1);                    RtlCopyMemory((PVOID)ResultInfo, &Ret, sizeof(ULONG_PTR));                }                _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)                {                    Ret = FALSE;                    _SEH2_YIELD(break);                }                _SEH2_END;            }            break;        }

函数中定义的消息如下:

// FNID's for NtUserSetWindowFNID, NtUserMessageCall#define FNID_FIRST                  0x029A#define FNID_SCROLLBAR              0x029A#define FNID_ICONTITLE              0x029B#define FNID_MENU                   0x029C#define FNID_DESKTOP                0x029D#define FNID_DEFWINDOWPROC          0x029E#define FNID_MESSAGEWND             0x029F#define FNID_SWITCH                 0x02A0#define FNID_BUTTON                 0x02A1#define FNID_COMBOBOX               0x02A2#define FNID_COMBOLBOX              0x02A3#define FNID_DIALOG                 0x02A4#define FNID_EDIT                   0x02A5#define FNID_LISTBOX                0x02A6#define FNID_MDICLIENT              0x02A7#define FNID_STATIC                 0x02A8#define FNID_IME                    0x02A9#define FNID_GHOST                  0x02AA#define FNID_CALLWNDPROC            0x02AB#define FNID_CALLWNDPROCRET         0x02AC#define FNID_HKINLPCWPEXSTRUCT      0x02AD#define FNID_HKINLPCWPRETEXSTRUCT   0x02AE#define FNID_MB_DLGPROC             0x02AF#define FNID_MDIACTIVATEDLGPROC     0x02B0#define FNID_SENDMESSAGE            0x02B1#define FNID_SENDMESSAGEFF          0x02B2// Kernel has option to use TimeOut or normal msg send, based on type of msg.#define FNID_SENDMESSAGEWTOOPTION   0x02B3#define FNID_SENDMESSAGECALLPROC    0x02B4#define FNID_BROADCASTSYSTEMMESSAGE 0x02B5#define FNID_TOOLTIPS               0x02B6#define FNID_SENDNOTIFYMESSAGE      0x02B7#define FNID_SENDMESSAGECALLBACK    0x02B8#define FNID_LAST                   0x02B9

接着我们来研究下内核如何传递这个消息的,来看看内部的co_IntDoSendMessage
同样函数中我们判断句柄是否有效,我们根据窗口句柄使用UserGetWindowObject 来查找窗体wnd,并判断窗体是否存在。如果不存在返回。这个是从全局句柄表中查找的窗体

static LRESULT FASTCALLco_IntDoSendMessage( HWND hWnd,                     UINT Msg,                     WPARAM wParam,                     LPARAM lParam,                     PDOSENDMESSAGE dsm){    LRESULT Result = TRUE;    NTSTATUS Status;    PWND Window = NULL;    MSG UserModeMsg, KernelModeMsg;    PMSGMEMORY MsgMemoryEntry;    PTHREADINFO ptiSendTo;    if (hWnd != HWND_BROADCAST && hWnd != HWND_TOPMOST)    {        Window = UserGetWindowObject(hWnd);        if ( !Window )        {            return 0;        }    }    /* Check for an exiting window. */    if (Window && Window->state & WNDS_DESTROYED)    {        ERR("co_IntDoSendMessage Window Exiting!\n");    }    /* See if the current thread can handle this message */    ptiSendTo = IntSendTo(Window, gptiCurrent, Msg);    // If broadcasting or sending to another thread, save the users data.    if (!Window || ptiSendTo )    {       UserModeMsg.hwnd    = hWnd;       UserModeMsg.message = Msg;       UserModeMsg.wParam  = wParam;       UserModeMsg.lParam  = lParam;       MsgMemoryEntry = FindMsgMemory(UserModeMsg.message);       Status = CopyMsgToKernelMem(&KernelModeMsg, &UserModeMsg, MsgMemoryEntry);       if (!NT_SUCCESS(Status))       {          EngSetLastError(ERROR_INVALID_PARAMETER);          return (dsm ? 0 : -1);       }    }    else    {       KernelModeMsg.hwnd    = hWnd;       KernelModeMsg.message = Msg;       KernelModeMsg.wParam  = wParam;       KernelModeMsg.lParam  = lParam;    }    if (!dsm)    {       Result = co_IntSendMessage( KernelModeMsg.hwnd,                                   KernelModeMsg.message,                                   KernelModeMsg.wParam,                                   KernelModeMsg.lParam );    }    else    {       Result = co_IntSendMessageTimeout( KernelModeMsg.hwnd,                                          KernelModeMsg.message,                                          KernelModeMsg.wParam,                                          KernelModeMsg.lParam,                                          dsm->uFlags,                                          dsm->uTimeout,                                         &dsm->Result );    }    if (!Window || ptiSendTo )    {       Status = CopyMsgToUserMem(&UserModeMsg, &KernelModeMsg);       if (!NT_SUCCESS(Status))       {          EngSetLastError(ERROR_INVALID_PARAMETER);          return(dsm ? 0 : -1);       }    }    return (LRESULT)Result;}

我们可以详细的分析co_IntSendMessageTimeout的内部实现,对于sendMessage来说,还是比较麻烦的,其内部有着很繁琐的嵌套程序,情况分类较多,可以广播send信息,也可以顶层窗口发送信息。

PostMessage

postmessage 使用的是win32k!NtUserPostMessage,其内部调用UserPostMessage
于是我们可以详细的分析一下这个函数,来看看系统是如何投递消息的。

BOOL FASTCALLUserPostMessage( HWND Wnd,                 UINT Msg,                 WPARAM wParam,                 LPARAM lParam ){    PTHREADINFO pti;    MSG Message, KernelModeMsg;    LARGE_INTEGER LargeTickCount;    Message.hwnd = Wnd;    Message.message = Msg;    Message.wParam = wParam;    Message.lParam = lParam;    Message.pt = gpsi->ptCursor;    KeQueryTickCount(&LargeTickCount);    Message.time = MsqCalculateMessageTime(&LargeTickCount);    if (is_pointer_message(Message.message))    {        EngSetLastError(ERROR_MESSAGE_SYNC_ONLY );        return FALSE;    }    if( Msg >= WM_DDE_FIRST && Msg <= WM_DDE_LAST )    {        NTSTATUS Status;        PMSGMEMORY MsgMemoryEntry;        MsgMemoryEntry = FindMsgMemory(Message.message);        Status = CopyMsgToKernelMem(&KernelModeMsg, &Message, MsgMemoryEntry);        if (! NT_SUCCESS(Status))        {            EngSetLastError(ERROR_INVALID_PARAMETER);            return FALSE;        }        co_IntSendMessageNoWait(KernelModeMsg.hwnd,                                KernelModeMsg.message,                                KernelModeMsg.wParam,                                KernelModeMsg.lParam);        if (MsgMemoryEntry && KernelModeMsg.lParam)            ExFreePool((PVOID) KernelModeMsg.lParam);        return TRUE;    }    if (!Wnd)    {//给当前线程发送消息        pti = PsGetCurrentThreadWin32Thread();        return UserPostThreadMessage( pti,                                      Msg,                                      wParam,                                      lParam);    }    if (Wnd == HWND_BROADCAST)    {        HWND *List;        PWND DesktopWindow;        ULONG i;        DesktopWindow = UserGetDesktopWindow();        List = IntWinListChildren(DesktopWindow);        if (List != NULL)        {//遍历所有窗口发送消息            UserPostMessage(DesktopWindow->head.h, Msg, wParam, lParam);            for (i = 0; List[i]; i++)            {                PWND pwnd = UserGetWindowObject(List[i]);                if (!pwnd) continue;                if ( pwnd->fnid == FNID_MENU || // Also need pwnd->pcls->atomClassName == gaOleMainThreadWndClass                     pwnd->pcls->atomClassName == gpsi->atomSysClass[ICLS_SWITCH] )                   continue;                UserPostMessage(List[i], Msg, wParam, lParam);            }            ExFreePoolWithTag(List, USERTAG_WINDOWLIST);        }    }    else    {        PWND Window;        Window = UserGetWindowObject(Wnd);        if ( !Window )        {            ERR("UserPostMessage: Invalid handle 0x%p Msg %d!\n",Wnd,Msg);            return FALSE;        }        pti = Window->head.pti;        if ( pti->TIF_flags & TIF_INCLEANUP )        {            ERR("Attempted to post message to window %p when the thread is in cleanup!\n", Wnd);            return FALSE;        }        if ( Window->state & WNDS_DESTROYED )        {            ERR("Attempted to post message to window %p that is being destroyed!\n", Wnd);            /* FIXME: Last error code? */            return FALSE;        }        if (WM_QUIT == Msg)        {            MsqPostQuitMessage(pti, wParam);        }        else        {            MsqPostMessage(pti, &Message, FALSE, QS_POSTMESSAGE, 0);        }    }    return TRUE;}

函数中我们判断当前的wnd窗口参数是否为空,如果为空,说明我们投递的是当前线程的消息,我们获得当前线程的win32线程信息,调用UserPostThreadMessage 向当前的线程队列中安置消息,其函数内部调用MsqPostMessage. 如果wnd 参数是HWND_BROADCAST,说明这是一个广播消息,于是我们获取桌面DesktopWindow这个顶级窗口,从这个窗口开始形成一个窗口列表,然后遍历这个列表,使用UserPostMessage向每个窗口的消息队列中投递消息。
如果不是广播消息,我们通过UserGetWindowObject来得到窗口WND对象,通过window->head.pti获得到窗口所在线程的线程信息结构体。根据线程信息判断这个这个线程是否正在清理或者是销毁,如果是则返回失败。如果不是,我们判断这个消息是不是退出消息,如果是,我们使用特殊的MsqPostQuitMessage函数来标记信息退出,如果这个消息不是退出消息我们同样使用MsqPostMessage来投递Post信息。

postmessage 从界面上向下调用经历了user32.dll 和win32k.sys,在win32k.sys 中,内部的核心函数为MsqPostMessage来做具体的消息投递。下面我们来分析一下这个函数。

VOID FASTCALLMsqPostMessage(PTHREADINFO pti,               MSG* Msg,               BOOLEAN HardwareMessage,               DWORD MessageBits,               DWORD dwQEvent){   PUSER_MESSAGE Message;   PUSER_MESSAGE_QUEUE MessageQueue;   if ( pti->TIF_flags & TIF_INCLEANUP || pti->MessageQueue->QF_flags & QF_INDESTROY )   {      ERR("Post Msg; Thread or Q is Dead!\n");      return;   }   if(!(Message = MsqCreateMessage(Msg)))    //我们创建消息   {      return;   }   MessageQueue = pti->MessageQueue;   if (dwQEvent)   {       ERR("Post Msg; System Qeued Event Message!\n");       InsertHeadList(&pti->PostedMessagesListHead,                      &Message->ListEntry);   }   else if (!HardwareMessage)   {       InsertTailList(&pti->PostedMessagesListHead,                      &Message->ListEntry);                                                                //排队消息   }   else   {       InsertTailList(&MessageQueue->HardwareMessagesListHead,                      &Message->ListEntry);   }   if (Msg->message == WM_HOTKEY) MessageBits |= QS_HOTKEY; // Justin Case, just set it.   Message->dwQEvent = dwQEvent;   Message->QS_Flags = MessageBits;   Message->pti = pti;   MsqWakeQueue(pti, MessageBits, TRUE);                                //设置消息位}

我们的PostQuitMessage函数调用,ntuser中如下实现,主要是修改线程信息结构体的内部信息。

VOID FASTCALLMsqPostQuitMessage(PTHREADINFO pti, ULONG ExitCode){   pti->QuitPosted = TRUE;   pti->exitCode = ExitCode;   MsqWakeQueue(pti, QS_POSTMESSAGE|QS_ALLPOSTMESSAGE, TRUE);                //设置对应消息位计数}

我们主要是控制_THREADINFO这个信息,内部放有消息队列。

typedef struct _THREADINFO{    W32THREAD;    PTL                 ptl;    PPROCESSINFO        ppi;    struct _USER_MESSAGE_QUEUE* MessageQueue;    struct tagKL*       KeyboardLayout;    PCLIENTTHREADINFO   pcti;    struct _DESKTOP*    rpdesk;    PDESKTOPINFO        pDeskInfo;    PCLIENTINFO         pClientInfo;    FLONG               TIF_flags;    PUNICODE_STRING     pstrAppName;    /* Messages that are currently dispatched to other threads */    LIST_ENTRY          DispatchingMessagesHead; // psmsSent    struct _USER_SENT_MESSAGE *pusmCurrent;    /* Queue of messages sent to the queue. */    LIST_ENTRY          SentMessagesListHead;    // psmsReceiveList    /* Last time PeekMessage() was called. */    LONG                timeLast;    ULONG_PTR           idLast;    /* True if a WM_QUIT message is pending. */    BOOLEAN             QuitPosted;    /* The quit exit code. */    INT                 exitCode;    HDESK               hdesk;    UINT                cPaintsReady; /* Count of paints pending. */    UINT                cTimersReady; /* Count of timers pending. */    DWORD               dwExpWinVer;    DWORD               dwCompatFlags;    DWORD               dwCompatFlags2;    struct _USER_MESSAGE_QUEUE* pqAttach;    PTHREADINFO         ptiSibling;    ULONG               fsHooks;    PHOOK               sphkCurrent;    LPARAM              lParamHkCurrent;    WPARAM              wParamHkCurrent;    struct tagSBTRACK*  pSBTrack;    /* Set if there are new messages specified by WakeMask in any of the queues. */    HANDLE              hEventQueueClient;    /* Handle for the above event (in the context of the process owning the queue). */    PKEVENT             pEventQueueServer;    LIST_ENTRY          PtiLink;    INT                 iCursorLevel;    POINT               ptLast;    /* Queue of messages posted to the queue. */    LIST_ENTRY          PostedMessagesListHead; // mlPost    UINT                fsChangeBitsRemoved;    UINT                cWindows;    UINT                cVisWindows;    LIST_ENTRY          aphkStart[NB_HOOKS];    CLIENTTHREADINFO    cti;  // Used only when no Desktop or pcti NULL.    /* ReactOS */    /* Thread Queue state tracking */    // Send list QS_SENDMESSAGE    // Post list QS_POSTMESSAGE|QS_HOTKEY|QS_PAINT|QS_TIMER|QS_KEY    // Hard list QS_MOUSE|QS_KEY only    // Accounting of queue bit sets, the rest are flags. QS_TIMER QS_PAINT counts are handled in thread information.    DWORD nCntsQBits[QSIDCOUNTS]; // QS_KEY QS_MOUSEMOVE QS_MOUSEBUTTON QS_POSTMESSAGE QS_SENDMESSAGE QS_HOTKEY    /* Messages that are currently dispatched by this message queue, required for cleanup */    LIST_ENTRY LocalDispatchingMessagesHead;    LIST_ENTRY WindowListHead;    LIST_ENTRY W32CallbackListHead;    SINGLE_LIST_ENTRY  ReferencesList;    ULONG cExclusiveLocks;#if DBG    USHORT acExclusiveLockCount[GDIObjTypeTotal + 1];#endif} THREADINFO;

DispatchMessageW

我们来看一下消息的派发,sendmessage 和 postmessage 是用户主动发送消息,这个函数是将消息派发到处理函数中。

typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt;} MSG,*LPMSG,*PMSG;

消息结构如上,里面有窗口句柄,消息号,两个消息参数,以及消息时间和位置。
于是乎函数中我们先判断消息号是否大于最大消息号码
然后我们通过窗口句柄获得窗口对象wnd。
之后我们判断是否是系统timer或是timer,并且lParam不空,于是我们通过lParam得到过程处理函数。如果是systimer,我们使用NtUserDispatchMessage向系统继续派发消息。否则我们验证一下这个回调函数(即在timer列表中判断是否存在这个timer处理函数)然后我们调用这个函数。
如果是其他消息,我们在user32一层先进行一次处理,判断出不是绘制消息,我们使用IntCallMessageProc 在user32层进行一下调用
如果是WM_PAINT并且窗口状态WNDS_SERVERSIDEWINDOWPROC我们将消息派发到系统中,使用函数
NtUserDispatchMessage。

LRESULTWINAPIDECLSPEC_HOTPATCHDispatchMessageW(CONST MSG *lpmsg){    LRESULT Ret = 0;    PWND Wnd;    BOOL Hit = FALSE;    if ( lpmsg->message & ~WM_MAXIMUM )    {        SetLastError( ERROR_INVALID_PARAMETER );        return 0;    }    if (lpmsg->hwnd != NULL)    {        Wnd = ValidateHwnd(lpmsg->hwnd);        if (!Wnd) return 0;    }    else        Wnd = NULL;    if (is_pointer_message(lpmsg->message))    {       SetLastError( ERROR_MESSAGE_SYNC_ONLY );       return 0;    }    if ((lpmsg->message == WM_TIMER || lpmsg->message == WM_SYSTIMER) && lpmsg->lParam != 0)    {        WNDPROC WndProc = (WNDPROC)lpmsg->lParam;        if ( lpmsg->message == WM_SYSTIMER )           return NtUserDispatchMessage( (PMSG) lpmsg );            //系统timer 消息我们继续派发        if (!NtUserValidateTimerCallback(lpmsg->hwnd, lpmsg->wParam, lpmsg->lParam))        {           WARN("Validating Timer Callback failed!\n");           return 0;        }       _SEH2_TRY       {           Ret = WndProc(lpmsg->hwnd,                        //调用用户注册的消息处理函数                         lpmsg->message,                         lpmsg->wParam,                         GetTickCount());       }       _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)       {          Hit = TRUE;       }       _SEH2_END;    }    else if (Wnd != NULL)    {       if ( (lpmsg->message != WM_PAINT) && !(Wnd->state & WNDS_SERVERSIDEWINDOWPROC) )        {           Ret = IntCallMessageProc(Wnd,                                    lpmsg->hwnd,                                    lpmsg->message,                                    lpmsg->wParam,                                    lpmsg->lParam,                                    FALSE);       }       else         Ret = NtUserDispatchMessage( (PMSG) lpmsg );    }    if (Hit)    {       WARN("Exception in Timer Callback WndProcW!\n");    }    return Ret;}

如果用不上系统的功能,此处直接进行的是user32处理,内部判断ansi还是unicode,不同的编码进行不同的处理,在IntCallWindowProc中我们依旧是判断编码,判断是否hook以调用hook函数,之后调用wnd->lpfnWndProc.

static LRESULT WINAPIIntCallMessageProc(IN PWND Wnd, IN HWND hWnd, IN UINT Msg, IN WPARAM wParam, IN LPARAM lParam, IN BOOL Ansi){    WNDPROC WndProc;    BOOL IsAnsi;    PCLS Class;    Class = DesktopPtrToUser(Wnd->pcls);    WndProc = NULL;    if ( Wnd->head.pti != GetW32ThreadInfo())    {  // Must be inside the same thread!       SetLastError( ERROR_MESSAGE_SYNC_ONLY );       return 0;    }  /*      This is the message exchange for user32. If there's a need to monitor messages,      do it here!   */    TRACE("HWND %p, MSG %u, WPARAM %p, LPARAM %p, Ansi %d\n", hWnd, Msg, wParam, lParam, Ansi);//    if (Class->fnid <= FNID_GHOST && Class->fnid >= FNID_BUTTON )    if (Class->fnid <= FNID_GHOST && Class->fnid >= FNID_FIRST )    {//提取出函数       if (Ansi)       {          if (GETPFNCLIENTW(Class->fnid) == Wnd->lpfnWndProc)             WndProc = GETPFNCLIENTA(Class->fnid);       }       else       {          if (GETPFNCLIENTA(Class->fnid) == Wnd->lpfnWndProc)             WndProc = GETPFNCLIENTW(Class->fnid);       }       IsAnsi = Ansi;       if (!WndProc)       {          IsAnsi = !Wnd->Unicode;          WndProc = Wnd->lpfnWndProc;       }    }    else    {       IsAnsi = !Wnd->Unicode;       WndProc = Wnd->lpfnWndProc;    }/*   Message caller can be Ansi/Unicode and the receiver can be Unicode/Ansi or   the same. */    if (!Ansi)        return IntCallWindowProcW(IsAnsi, WndProc, Wnd, hWnd, Msg, wParam, lParam);    else        return IntCallWindowProcA(IsAnsi, WndProc, Wnd, hWnd, Msg, wParam, lParam);}

接下来我们看看内核的消息派发即NtUserDispatchMessage,当然每次进入内核的调用首先都要转换用户参数到内核参数,转接内核函数IntDispatchMessage
我们在NtUserDispatchMessage中调用内部函数IntDispatchMessage来具体处理系统消息的派发。

LRESULT FASTCALLIntDispatchMessage(PMSG pMsg){    LARGE_INTEGER TickCount;    LONG Time;    LRESULT retval = 0;    PTHREADINFO pti;    PWND Window = NULL;    BOOL DoCallBack = TRUE;    if (pMsg->hwnd)    {        Window = UserGetWindowObject(pMsg->hwnd);        if (!Window) return 0;    }    pti = PsGetCurrentThreadWin32Thread();    if ( Window && Window->head.pti != pti)    {       EngSetLastError( ERROR_MESSAGE_SYNC_ONLY );       return 0;    }    if (((pMsg->message == WM_SYSTIMER) ||         (pMsg->message == WM_TIMER)) &&         (pMsg->lParam) )    {        if (pMsg->message == WM_TIMER)        {            if (ValidateTimerCallback(pti,pMsg->lParam))            {                KeQueryTickCount(&TickCount);                Time = MsqCalculateMessageTime(&TickCount);                retval = co_IntCallWindowProc((WNDPROC)pMsg->lParam,                                              TRUE,                                              pMsg->hwnd,                                              WM_TIMER,                                              pMsg->wParam,                                              (LPARAM)Time,                                              -1);            }            return retval;        }        else        {            PTIMER pTimer = FindSystemTimer(pMsg);            if (pTimer && pTimer->pfn)            {                KeQueryTickCount(&TickCount);                Time = MsqCalculateMessageTime(&TickCount);                pTimer->pfn(pMsg->hwnd, WM_SYSTIMER, (UINT)pMsg->wParam, Time);            }            return 0;        }    }    // Need a window!    if ( !Window ) return 0;    if (pMsg->message == WM_PAINT) Window->state |= WNDS_PAINTNOTPROCESSED;    if ( Window->state & WNDS_SERVERSIDEWINDOWPROC )    {       TRACE("Dispatch: Server Side Window Procedure\n");       switch(Window->fnid)       {          case FNID_DESKTOP:            DoCallBack = !DesktopWindowProc( Window,                                             pMsg->message,                                             pMsg->wParam,                                             pMsg->lParam,                                            &retval);            break;          case FNID_MESSAGEWND:            DoCallBack = !UserMessageWindowProc( Window,                                                 pMsg->message,                                                 pMsg->wParam,                                                 pMsg->lParam,                                                 &retval);            break;       }    }    /* Since we are doing a callback on the same thread right away, there is       no need to copy the lparam to kernel mode and then back to usermode.       We just pretend it isn't a pointer */    if (DoCallBack)    retval = co_IntCallWindowProc( Window->lpfnWndProc,                                   !Window->Unicode,                                   pMsg->hwnd,                                   pMsg->message,                                   pMsg->wParam,                                   pMsg->lParam,                                   -1);    if (pMsg->message == WM_PAINT)    {        PREGION Rgn;        Window->state2 &= ~WNDS2_WMPAINTSENT;        /* send a WM_NCPAINT and WM_ERASEBKGND if the non-client area is still invalid */        Rgn = IntSysCreateRectpRgn( 0, 0, 0, 0 );        co_UserGetUpdateRgn( Window, Rgn, TRUE );        REGION_Delete(Rgn);    }    return retval;}

从函数中我们获知,在函数开始我们获得到窗口结构wnd,然后得到线程信息threadinfo,做前期的数据结构的准备工作。
然后我们判断信息类型,如果是WM_TIMER信息,我们验证回调函数是否可用,然后计算下timer,使用co_IntCallWindowProc进行处理函数调用,传递WM_TIMER参数
如果是系统timer消息,即WM_SYSTIMER,我们在TimersListHead系统timer链表中找到这个timer,计算timer时间,调用位于timer结构中的处理函数。
如果是WM_PAINT消息,我们重新绘制,使用co_UserGetUpdateRgn来更新

GetMessageW

同样我们的getmessage依旧需要和win32k交互获得信息,因为获得信息是从队列中获得的,这个队列存在内核win32子系统中。我们内部使用的是NtUserGetMessage 进行实际操作的是co_IntGetPeekMessage.我们如下调用这个函数
co_IntGetPeekMessage(&Msg, hWnd, MsgFilterMin, MsgFilterMax, PM_REMOVE, TRUE);
PM_REMOVE从队列中移除这个消息。

系统中定义如下:可以从队列中移除或不移除,也可以

#define PM_NOREMOVE 0#define PM_REMOVE 1#define PM_NOYIELD 2            此标志使系统不释放等待调用程序空闲的线程
BOOL FASTCALLco_IntGetPeekMessage( PMSG pMsg,                      HWND hWnd,                      UINT MsgFilterMin,                      UINT MsgFilterMax,                      UINT RemoveMsg,                      BOOL bGMSG ){    PWND Window;    PTHREADINFO pti;    BOOL Present = FALSE;    NTSTATUS Status;    if ( hWnd == HWND_TOPMOST || hWnd == HWND_BROADCAST )        hWnd = HWND_BOTTOM;    /* Validate input */    if (hWnd && hWnd != HWND_BOTTOM)    {        if (!(Window = UserGetWindowObject(hWnd)))        {            if (bGMSG)                return -1;            else                return FALSE;        }    }    else    {        Window = (PWND)hWnd;    }    if (MsgFilterMax < MsgFilterMin)    {        MsgFilterMin = 0;        MsgFilterMax = 0;    }    if (bGMSG)    {       RemoveMsg |= ((GetWakeMask( MsgFilterMin, MsgFilterMax ))<< 16);    }    pti = PsGetCurrentThreadWin32Thread();    pti->pClientInfo->cSpins++; // Bump up the spin count.    do    {        Present = co_IntPeekMessage( pMsg,                                     Window,                                     MsgFilterMin,                                     MsgFilterMax,                                     RemoveMsg,                                     bGMSG );        if (Present)        {           /* GetMessage or PostMessage must never get messages that contain pointers */           ASSERT(FindMsgMemory(pMsg->message) == NULL);           if (pMsg->message != WM_PAINT && pMsg->message != WM_QUIT)           {              pti->timeLast = pMsg->time;              pti->ptLast   = pMsg->pt;           }           // The WH_GETMESSAGE hook enables an application to monitor messages about to           // be returned by the GetMessage or PeekMessage function.           co_HOOK_CallHooks( WH_GETMESSAGE, HC_ACTION, RemoveMsg & PM_REMOVE, (LPARAM)pMsg);           if ( bGMSG ) break;        }        if ( bGMSG )        {            Status = co_MsqWaitForNewMessages( pti,                                               Window,                                               MsgFilterMin,                                               MsgFilterMax);           if ( !NT_SUCCESS(Status) ||                Status == STATUS_USER_APC ||                Status == STATUS_TIMEOUT )           {              Present = -1;              break;           }        }        else        {           if (!(RemoveMsg & PM_NOYIELD))           {              IdlePing();              // Yield this thread!              UserLeave();              ZwYieldExecution();              UserEnterExclusive();              // Fall through to exit.              IdlePong();           }           break;        }    }    while( bGMSG && !Present );    // Been spinning, time to swap vinyl...    if (pti->pClientInfo->cSpins >= 100)    {       // Clear the spin cycle to fix the mix.       pti->pClientInfo->cSpins = 0;       //if (!(pti->TIF_flags & TIF_SPINNING)) // FIXME: Need to swap vinyl...    }    return Present;}

一般情况下,在自己的程序中,我们如下设置消息循环。我们使用GetMessage 最后两个参数传递为两个0,即代表获取所有消息。

while( ::GetMessage( &msg, NULL, 0, 0 ) )  {        if( !CPaintManagerUI::TranslateMessage( &msg ) )         {            ::TranslateMessage(&msg); // 用户输入消息            ::DispatchMessage(&msg);        }}

所以接着调用这个核心函数的时候,我们多数情况下是获取所有消息

BOOL FASTCALLco_IntPeekMessage( PMSG Msg,                   PWND Window,                   UINT MsgFilterMin,                   UINT MsgFilterMax,                   UINT RemoveMsg,                   BOOL bGMSG ){    PTHREADINFO pti;    LARGE_INTEGER LargeTickCount;    BOOL RemoveMessages;    UINT ProcessMask;    BOOL Hit = FALSE;    pti = PsGetCurrentThreadWin32Thread();    RemoveMessages = RemoveMsg & PM_REMOVE;    ProcessMask = HIWORD(RemoveMsg); /* Hint, "If wMsgFilterMin and wMsgFilterMax are both zero, PeekMessage returns    all available messages (that is, no range filtering is performed)".        */    if (!ProcessMask) ProcessMask = (QS_ALLPOSTMESSAGE|QS_ALLINPUT);    IdlePong();    do    {        KeQueryTickCount(&LargeTickCount);        pti->timeLast = LargeTickCount.u.LowPart;        pti->pcti->tickLastMsgChecked = LargeTickCount.u.LowPart;        /* Dispatch sent messages here. */        while ( co_MsqDispatchOneSentMessage(pti) )        {           /* if some PM_QS* flags were specified, only handle sent messages from now on */           if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE; // wine does this; ProcessMask = QS_SENDMESSAGE;        }        if (Hit) return FALSE;        /* Clear changed bits so we can wait on them if we don't find a message */        if (ProcessMask & QS_POSTMESSAGE)        {           pti->pcti->fsChangeBits &= ~(QS_POSTMESSAGE | QS_HOTKEY | QS_TIMER);           if (MsgFilterMin == 0 && MsgFilterMax == 0) // Wine hack does this; ~0U)           {              pti->pcti->fsChangeBits &= ~QS_ALLPOSTMESSAGE;           }        }        if (ProcessMask & QS_INPUT)        {           pti->pcti->fsChangeBits &= ~QS_INPUT;        }        /* Now check for normal messages. */        if (( (ProcessMask & QS_POSTMESSAGE) ||              (ProcessMask & QS_HOTKEY) ) &&            MsqPeekMessage( pti,                            RemoveMessages,                            Window,                            MsgFilterMin,                            MsgFilterMax,                            ProcessMask,                            Msg ))        {               return TRUE;        }        /* Now look for a quit message. */        if (pti->QuitPosted)        {            /* According to the PSDK, WM_QUIT messages are always returned, regardless               of the filter specified */            Msg->hwnd = NULL;            Msg->message = WM_QUIT;            Msg->wParam = pti->exitCode;            Msg->lParam = 0;            if (RemoveMessages)            {                pti->QuitPosted = FALSE;                ClearMsgBitsMask(pti, QS_POSTMESSAGE);                pti->pcti->fsWakeBits &= ~QS_ALLPOSTMESSAGE;                pti->pcti->fsChangeBits &= ~QS_ALLPOSTMESSAGE;            }            return TRUE;        }        /* Check for hardware events. */        if ((ProcessMask & QS_INPUT) &&            co_MsqPeekHardwareMessage( pti,                                       RemoveMessages,                                       Window,                                       MsgFilterMin,                                       MsgFilterMax,                                       ProcessMask,                                       Msg))        {            return TRUE;        }        if ((ProcessMask & QS_MOUSE) &&            co_MsqPeekMouseMove( pti,                                 RemoveMessages,                                 Window,                                 MsgFilterMin,                                 MsgFilterMax,                                 Msg ))        {            return TRUE;        }        /* Check for sent messages again. */        while ( co_MsqDispatchOneSentMessage(pti) )        {           if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE;        }        if (Hit) return FALSE;        /* Check for paint messages. */        if ((ProcessMask & QS_PAINT) &&            pti->cPaintsReady &&            IntGetPaintMessage( Window,                                MsgFilterMin,                                MsgFilterMax,                                pti,                                Msg,                                RemoveMessages))        {            return TRUE;        }       /* This is correct, check for the current threads timers waiting to be          posted to this threads message queue. If any we loop again.        */        if ((ProcessMask & QS_TIMER) &&            PostTimerMessages(Window))        {            continue;        }        return FALSE;    }    while (TRUE);    return TRUE;}

从函数中我们可以获知,系统是如何捡取消息的。
首先系统派发send message ,调用co_MsqDispatchOneSentMessage来派发消息。
然后系统处理POSTMESSAGE和HOTKEY,调用MsqPeekMessage来处理
接着系统查看处理退出信息,即quit message,这个信息你可以用postquitmessage来置位。
我们处理硬件输入信息co_MsqPeekHardwareMessage
处理鼠标信息co_MsqPeekMouseMove
又一次处理此时没有有发送信息即sendmessage。
接着处理绘制信息即PAINT,调用IntGetPaintMessage处理
最后的时候处理timer信息,PostTimerMessages。

MsqPeekMessage

正常消息的提取需要这个函数 提取POSTMESSAGE和HOTKEY消息
我们现在来看看这个函数,是如何来提取消息的
主要是检索队列 这个队列位于pti->PostedMessagesListHead中

BOOLEAN APIENTRYMsqPeekMessage(IN PTHREADINFO pti,                  IN BOOLEAN Remove,                  IN PWND Window,                  IN UINT MsgFilterLow,                  IN UINT MsgFilterHigh,                  IN UINT QSflags,                  OUT PMSG Message){   PLIST_ENTRY CurrentEntry;   PUSER_MESSAGE CurrentMessage;   PLIST_ENTRY ListHead;   BOOL Ret = FALSE;   CurrentEntry = pti->PostedMessagesListHead.Flink;   ListHead = &pti->PostedMessagesListHead;   if (IsListEmpty(CurrentEntry)) return FALSE;   CurrentMessage = CONTAINING_RECORD(CurrentEntry, USER_MESSAGE,                                         ListEntry);   do   {      if (IsListEmpty(CurrentEntry)) break;      if (!CurrentMessage) break;      CurrentEntry = CurrentEntry->Flink;/* MSDN: 1: any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL. 2: retrieves only messages on the current thread's message queue whose hwnd value is NULL. 3: handle to the window whose messages are to be retrieved. */      if ( ( !Window || // 1            ( Window == PWND_BOTTOM && CurrentMessage->Msg.hwnd == NULL ) || // 2            ( Window != PWND_BOTTOM && Window->head.h == CurrentMessage->Msg.hwnd ) ) && // 3            ( ( ( MsgFilterLow == 0 && MsgFilterHigh == 0 ) && CurrentMessage->QS_Flags & QSflags ) ||              ( MsgFilterLow <= CurrentMessage->Msg.message && MsgFilterHigh >= CurrentMessage->Msg.message ) ) )      {         *Message = CurrentMessage->Msg;         if (Remove)         {             RemoveEntryList(&CurrentMessage->ListEntry);             ClearMsgBitsMask(pti, CurrentMessage->QS_Flags);             MsqDestroyMessage(CurrentMessage);         }         Ret = TRUE;         break;      }      CurrentMessage = CONTAINING_RECORD(CurrentEntry, USER_MESSAGE,                                         ListEntry);   }   while (CurrentEntry != ListHead);   return Ret;}
0 0
原创粉丝点击