windows message manager

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 FASTCALL
co_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 FASTCALL
UserPostMessage( 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 FASTCALL
MsqPostMessage(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 FASTCALL
MsqPostQuitMessage(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。

LRESULT
WINAPI
DECLSPEC_HOTPATCH
DispatchMessageW(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 WINAPI
IntCallMessageProc(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 FASTCALL
IntDispatchMessage(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 FASTCALL
co_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 FASTCALL
co_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 APIENTRY
MsqPeekMessage(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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值