Reactos 第 7 章 视窗报文 — 7.2 视窗报文的接收

第 7 章 视窗报文 --- 7.2 视窗报文的接收

本节深入剖析 Windows/ReactOS 中"视窗报文"(Window Message)接收机制的完整实现,重点讲解 GetMessage/PeekMessage/DispatchMessage 的内核态处理流程。

概述

视窗报文的"接收"是消息循环(Message Loop)的核心。Windows GUI 应用程序的每一个线程都需要一个消息循环:

c 复制代码
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

这个看似简单的循环背后,win32k 内核需要处理:

  • 投递消息(Posted Messages)------ PostMessage 投递
  • 发送消息(Sent Messages)------ SendMessage 跨线程发送
  • 硬件消息(Hardware Messages)------ 键盘/鼠标原始输入
  • 内部事件(Internal Events)------ 系统内部触发的事件
  • WM_PAINT 消息 ------ 绘制消息
  • WM_TIMER 消息 ------ 定时器消息
  • WM_QUIT 消息 ------ 退出消息
  • 各种 hook 处理

视窗报文接收的本质是什么?

报文接收本质上是"消息分类与优先级调度":win32k 按照 MSDN 规定的优先级(SentMessages → PostedMessages → Hardware → SentMessages → Paint → Timer)依次扫描各种消息源,在合适的时机唤醒等待的线程,并通过 DispatchMessage 调用窗口过程。

想象一个邮递系统场景:

  • 投递消息(Posted):标准信封,按时间顺序放进收件人邮箱;
  • 发送消息(Sent):加急快递,需要收件人当面签收并带回执;
  • 硬件消息(Hardware):实时事件流(电话铃响),不能延迟;
  • 系统事件(Internal):内部通知(公司公告),不投递但需要立即处理;
  • WM_PAINT:重绘请求(清洁工来打扫),按需触发;
  • GetMessage:收件人去邮箱取件,按优先级逐封检查;
  • DispatchMessage:收件人拆开信封并按内容行事(调用 WndProc)。

本节内容概览

  1. 7.2.0 框架图:报文接收完整架构;
  2. 7.2.1 报文分类体系:5 种消息源与优先级;
  3. 7.2.2 USER_MESSAGE 结构:投递消息内核态表示;
  4. 7.2.3 GetMessage 完整流程:NtUserGetMessage 实现;
  5. 7.2.4 co_IntGetPeekMessage 调度核心:5 种消息源依次扫描;
  6. 7.2.5 co_MsqPeekHardwareMessage:硬件消息处理;
  7. 7.2.6 co_IntProcessMouseMessage:鼠标消息路由与双击判定;
  8. 7.2.7 co_IntProcessKeyboardMessage:键盘消息处理;
  9. 7.2.8 IntDispatchMessage:分发到 WndProc;
  10. 7.2.9 等待新消息:co_MsqWaitForNewMessages;
  11. 7.2.10 设计哲学问答:6 个关键设计问题解答。

学习目标

读完本节后,读者应当能够:

  • 理解 Windows 5 种消息源(Sent/Posted/Hardware/Internal/Paint)的优先级与处理顺序;
  • 掌握 GetMessage/PeekMessage 内核态实现细节;
  • 分析 DispatchMessage 调用 WndProc 的完整流程;
  • 理解硬件消息的捕获与分发机制;
  • 解释双击判定、键盘状态更新等细节;
  • 掌握消息循环的"等待-唤醒"机制。

涉及的内核子系统

子系统 职责
win32k.sys/ntuser 消息接收、分发、队列管理
win32k.sys/ntuser 硬件消息处理、键盘/鼠标路由
user32.dll 用户态 GetMessage/PeekMessage/DispatchMessage 包装
ntoskrnl/ke 事件对象、线程等待与唤醒

7.2.0 框架图

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────┐
│                    视窗报文接收完整架构                                               │
├──────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│   用户态层                                                                             │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  user32.dll                                                                │     │
│   │  ├─► GetMessageW / GetMessageA    (阻塞式获取)                             │     │
│   │  ├─► PeekMessageW / PeekMessageA  (非阻塞式)                              │     │
│   │  ├─► DispatchMessageW / DispatchMessageA  (分发给 WndProc)                 │     │
│   │  └─► TranslateMessage              (翻译键盘消息)                          │     │
│   │      │                                                                      │     │
│   │      ▼  (通过 NtUserMessageCall / NtUserGetMessage / NtUserPeekMessage)   │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                              │                                                       │
│                              ▼  (syscall)                                              │
│   内核态层                                                                             │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  win32k.sys/ntuser/message.c                                                │     │
│   │  ├─► NtUserGetMessage      ──► co_IntGetPeekMessage(bGMSG=TRUE)          │     │
│   │  ├─► NtUserPeekMessage     ──► co_IntGetPeekMessage(bGMSG=FALSE)         │     │
│   │  ├─► NtUserDispatchMessage ──► IntDispatchMessage                          │     │
│   │  └─► co_IntWaitMessage     ──► co_MsqWaitForNewMessages                  │     │
│   │                                                                             │     │
│   │  win32k.sys/ntuser/msgqueue.c                                                │     │
│   │  ├─► MsqPeekMessage            (投递消息扫描)                              │     │
│   │  ├─► co_MsqPeekHardwareMessage (硬件消息扫描)                              │     │
│   │  ├─► co_MsqDispatchOneSentMessage (发送消息分发)                           │     │
│   │  ├─► co_MsqWaitForNewMessages  (等待新消息 - 阻塞)                        │     │
│   │  ├─► co_IntProcessMouseMessage (鼠标消息路由)                              │     │
│   │  ├─► co_IntProcessKeyboardMessage (键盘消息处理)                           │     │
│   │  └─► co_MsqInsertMouseMessage   (插入硬件鼠标消息)                         │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                              │                                                       │
│                              ▼                                                       │
│   消息源                                                                              │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  1. 发送消息链表 pti->SentMessagesListHead  (SendMessage 跨线程)         │     │
│   │  2. 投递消息链表 pti->PostedMessagesListHead (PostMessage 异步)            │     │
│   │  3. 硬件消息链表 pti->MessageQueue->HardwareMessagesListHead               │     │
│   │  4. 内部事件链表 (WM_ASYNC_*, 系统通知)                                    │     │
│   │  5. 绘制消息 (pWnd->cPaintsReady > 0)                                      │     │
│   │  6. 定时器消息 (pWnd->spwndTimer list)                                     │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
│   优先级顺序(MSDN 规定)                                                              │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  ①  发送消息 (Sent)         ── 必须立即处理,否则可能死锁                 │     │
│   │  ②  投递消息 (Posted)       ── PostMessage、SendNotifyMessage             │     │
│   │  ③  硬件消息 (Hardware)     ── 键盘、鼠标原始输入                        │     │
│   │  ④  内部事件 (Internal)     ── 窗口激活、焦点变化等系统事件              │     │
│   │  ⑤  发送消息 (Sent) 再次    ── 避免发送消息被长时间阻塞                  │     │
│   │  ⑥  绘制消息 (Paint)        ── WM_PAINT、WM_NCPAINT                      │     │
│   │  ⑦  定时器消息 (Timer)      ── WM_TIMER、WM_SYSTIMER                     │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────┘

7.2.1 报文分类体系

7.2.1.1 5 种消息源

Windows 报文(消息)按产生方式和处理方式分为 5 类:

消息类型 产生方式 异步性 示例
发送消息(Sent) SendMessage(直接调用 WndProc)/ 跨线程发送 同步(可阻塞) WM_SETTEXT、WM_COPYDATA
投递消息(Posted) PostMessage 投递 异步 WM_USER+1、自定义消息
硬件消息(Hardware) 键盘/鼠标原始输入 异步 WM_KEYDOWN、WM_MOUSEMOVE
系统事件(Internal) win32k 内部事件 异步 WM_ASYNC_SETACTIVEWINDOW
绘制消息(Paint) 窗口区域失效 按需 WM_PAINT、WM_NCPAINT

7.2.1.2 投递消息(USER_MESSAGE)

win32ss/user/ntuser/msgqueue.h:8(file:///d:/reactos/win32ss/user/ntuser/msgqueue.h#L8):

c 复制代码
typedef struct _USER_MESSAGE
{
    LIST_ENTRY ListEntry;       // 链表节点
    MSG Msg;                    // 用户态可见的 MSG 结构
    DWORD QS_Flags;             // 该消息对应的 QS_* 标志
    LONG_PTR ExtraInfo;         // 消息附加信息
    DWORD dwQEvent;             // 队列事件类型
    PTHREADINFO pti;            // 接收者线程
} USER_MESSAGE, *PUSER_MESSAGE;

MSG 是用户态可见的标准消息结构:

c 复制代码
typedef struct tagMSG
{
    HWND hwnd;        // 目标窗口
    UINT message;     // 消息 ID
    WPARAM wParam;    // 参数 1
    LPARAM lParam;    // 参数 2
    DWORD time;       // 时间戳
    POINT pt;         // 鼠标位置
    DWORD lPrivate;   // 私有数据
} MSG, *PMSG;

7.2.1.3 发送消息(USER_SENT_MESSAGE)

win32ss/user/ntuser/msgqueue.h:20(file:///d:/reactos/win32ss/user/ntuser/msgqueue.h#L20):

c 复制代码
typedef struct _USER_SENT_MESSAGE
{
    LIST_ENTRY ListEntry;
    MSG Msg;
    DWORD QS_Flags;             // 原始 QS 位
    PKEVENT pkCompletionEvent;  // 同步完成事件
    LRESULT lResult;            // WndProc 返回值
    DWORD flags;                // 状态标志
    PTHREADINFO ptiSender;      // 发送者
    PTHREADINFO ptiReceiver;    // 接收者
    SENDASYNCPROC CompletionCallback;  // 异步完成回调
    PTHREADINFO ptiCallBackSender;
    ULONG_PTR CompletionCallbackContext;
    INT HookMessage;
    BOOL HasPackedLParam;
    KEVENT CompletionEvent;     // 内嵌完成事件
} USER_SENT_MESSAGE, *PUSER_SENT_MESSAGE;

SMF_* 标志(msgqueue.h:38(file:///d:/reactos/win32ss/user/ntuser/msgqueue.h#L38)):

c 复制代码
#define SMF_RECEIVERDIED    0x00000002
#define SMF_SENDERDIED      0x00000004
#define SMF_RECEIVERFREE    0x00000008
#define SMF_RECEIVEDMESSAGE 0x00000010
#define SMF_RECEIVERBUSY    0x00004000

7.2.1.4 QS_* 标志

QS_* 标志(Queue Status)表示线程消息队列中"可能有"的消息类型:

c 复制代码
#define QS_KEY              0x0001
#define QS_MOUSEMOVE        0x0002
#define QS_MOUSEBUTTON      0x0004
#define QS_POSTMESSAGE      0x0008
#define QS_TIMER            0x0010
#define QS_PAINT            0x0020
#define QS_SENDMESSAGE      0x0040
#define QS_HOTKEY           0x0080
#define QS_ALLPOSTMESSAGE   0x0100
#define QS_RAWINPUT         0x0400
#define QS_TOUCH            0x0800
#define QS_POINTER          0x1000
#define QS_EVENT            0x2000

// 组合宏
#define QS_INPUT           (QS_MOUSEMOVE | QS_MOUSEBUTTON | QS_KEY)
#define QS_ALLINPUT        (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE)

每个消息类型对应一个 QS 位,GetMessage/PeekMessage 可以根据 QS 位快速过滤。


7.2.2 USER_MESSAGE 内核表示

7.2.2.1 MsqCreateMessage

MsqCreateMessage 位于 win32ss/user/ntuser/msgqueue.c:730(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L730):

c 复制代码
PUSER_MESSAGE FASTCALL
MsqCreateMessage(LPMSG Msg)
{
    PUSER_MESSAGE Message;

    /* Allocate from the paged lookaside list */
    Message = ExAllocateFromPagedLookasideList(pgMessageLookasideList);
    if (!Message)
    {
        ERR("MsqCreateMessage() - Unable to allocate message\n");
        return NULL;
    }

    /* Initialize the message */
    RtlCopyMemory(&Message->Msg, Msg, sizeof(MSG));
    Message->pti = NULL;
    Message->QS_Flags = 0;
    Message->ExtraInfo = 0;
    Message->dwQEvent = 0;
    return Message;
}

关键点:

  • 使用 PagedLookasideList 加速分配(消息分配极频繁);
  • 复制 MSG 内容并清零其他字段;
  • 接收者 pti 在 MsqPostMessage 中设置。

7.2.2.2 MsqPostMessage

MsqPostMessage 位于 win32ss/user/ntuser/msgqueue.c:1336(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1336):

c 复制代码
VOID FASTCALL
MsqPostMessage(PTHREADINFO pti,
               MSG* Msg,
               BOOLEAN HardwareMessage,
               DWORD QS_Flags,
               DWORD QEvent,
               LONG_PTR ExtraInfo)
{
    PUSER_MESSAGE Message;

    Message = MsqCreateMessage(Msg);
    if (!Message) return;

    /* Set the message fields */
    Message->QS_Flags = QS_Flags;
    Message->dwQEvent = QEvent;
    Message->ExtraInfo = ExtraInfo;
    Message->pti = pti;

    /* Insert into the appropriate list */
    if (HardwareMessage)
    {
        InsertTailList(&pti->MessageQueue->HardwareMessagesListHead,
                        &Message->ListEntry);
    }
    else
    {
        InsertTailList(&pti->PostedMessagesListHead, &Message->ListEntry);
    }

    /* Update QS flags and wake the queue */
    if (QS_Flags)
    {
        SetMsgBitsMask(pti, QS_Flags);
    }
    pti->pcti->fsChangeBits |= QS_Flags;

    /* Wake the thread's message queue event */
    KeSetEvent(pti->pEventQueueServer, IO_NO_INCREMENT, FALSE);
}

关键点

  1. 分配 USER_MESSAGE;
  2. 根据 HardwareMessage 标志选择插入硬件链表或投递链表;
  3. 设置 QS_Flags 唤醒队列(更新 pcti->fsChangeBits);
  4. KeSetEvent 唤醒等待 GetMessage 的线程

7.2.2.3 消息链表组织

每个线程的消息队列维护 3 个核心链表:

复制代码
┌─────────────────────────────────────────────────────────────┐
│  PTHREADINFO                                                 │
│  ├─► PostedMessagesListHead     ─► 投递消息链表              │
│  └─► SentMessagesListHead       ─► 发送消息链表              │
│                                                              │
│  USER_MESSAGE_QUEUE.MessageQueue                             │
│  └─► HardwareMessagesListHead   ─► 硬件消息链表              │
└─────────────────────────────────────────────────────────────┘

7.2.2.4 QS_Flags 与 PCTI 镜像

每个线程的 PCTI(Per-Thread Info,即 TEB 的 win32 区域)维护 QS 标志位:

c 复制代码
typedef struct _CLIENTINFO
{
    // ...
    DWORD fsWakeBits;      // 当前可用的 QS 位(实时状态)
    DWORD fsChangeBits;    // 新到达的 QS 位(未读取)
    DWORD fsWakeMask;      // 当前等待的 QS 位
    DWORD fsWakeMaskJournaling;  // 唤醒掩码日志
} CLIENTINFO, *PCLIENTINFO;
  • fsWakeBits:线程当前"持有"的消息类型;
  • fsChangeBits:自上次 GetMessage 以来新增的消息类型;
  • fsWakeMask:当前等待的 QS 位(被 GetMessage 阻塞时设置)。

7.2.3 GetMessage 完整流程

7.2.3.1 NtUserGetMessage 入口

NtUserGetMessage 位于 win32ss/user/ntuser/message.c:2342(file:///d:/reactos/win32ss/user/ntuser/message.c#L2342):

c 复制代码
BOOL APIENTRY
NtUserGetMessage(PMSG pMsg,
                 HWND hWnd,
                 UINT MsgFilterMin,
                 UINT MsgFilterMax)
{
    BOOL Res;

    UserEnterShared();

    Res = co_IntGetPeekMessage(pMsg,
                                hWnd,
                                MsgFilterMin,
                                MsgFilterMax,
                                PM_REMOVE,
                                TRUE);  // bGMSG = TRUE (GetMessage)

    UserLeave();
    return Res;
}

GetMessage 与 PeekMessage 的核心区别:

  • GetMessage:bGMSG = TRUE,表示阻塞模式,无消息时挂起线程;
  • PeekMessage:bGMSG = FALSE,表示非阻塞,无消息时立即返回 FALSE。

7.2.3.2 NtUserPeekMessage 入口

c 复制代码
BOOL APIENTRY
NtUserPeekMessage(PMSG pMsg,
                  HWND hWnd,
                  UINT MsgFilterMin,
                  UINT MsgFilterMax,
                  UINT RemoveMsg)
{
    BOOL Res;

    UserEnterShared();

    Res = co_IntGetPeekMessage(pMsg,
                                hWnd,
                                MsgFilterMin,
                                MsgFilterMax,
                                RemoveMsg,
                                FALSE);  // bGMSG = FALSE (PeekMessage)

    UserLeave();
    return Res;
}

7.2.3.3 co_IntGetPeekMessage 完整实现

win32ss/user/ntuser/message.c:1225(file:///d:/reactos/win32ss/user/ntuser/message.c#L1225):

c 复制代码
BOOL APIENTRY
co_IntGetPeekMessage(PMSG pMsg,
                     HWND hWnd,
                     UINT MsgFilterMin,
                     UINT MsgFilterMax,
                     UINT RemoveMsg,
                     BOOL bGMSG)
{
    PWND Window;
    PTHREADINFO pti;
    BOOL Present = FALSE;
    NTSTATUS Status;
    LONG_PTR ExtraInfo = 0;

    /* 特殊 hWnd 转换 */
    if (hWnd == HWND_TOPMOST || hWnd == HWND_BROADCAST)
        hWnd = HWND_BOTTOM;

    /* 验证 hWnd */
    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;
    }

    /* GetMessage 时根据过滤器计算 WakeMask */
    if (bGMSG)
    {
       RemoveMsg |= ((GetWakeMask(MsgFilterMin, MsgFilterMax)) << 16);
    }

    pti = PsGetCurrentThreadWin32Thread();
    pti->pClientInfo->cSpins++; // 增加自旋计数

    do
    {
        Present = co_IntPeekMessage(pMsg,
                                    Window,
                                    MsgFilterMin,
                                    MsgFilterMax,
                                    RemoveMsg,
                                    &ExtraInfo,
                                    bGMSG);
        if (Present)
        {
           /* 一些断言检查 */
           if (pMsg->message != WM_DEVICECHANGE || (pMsg->wParam & 0x8000))
           {
               ASSERT(FindMsgMemory(pMsg->message) == NULL);
           }

           /* DDE 消息特殊处理 */
           if (pMsg->message >= WM_DDE_FIRST && pMsg->message <= WM_DDE_LAST)
           {
              if (!IntDdeGetMessageHook(pMsg, ExtraInfo))
              {
                 TRACE("DDE Get return ERROR\n");
                 continue;
              }
           }

           /* 更新最后位置 */
           if (pMsg->message != WM_PAINT && pMsg->message != WM_QUIT)
           {
              if (!RtlEqualMemory(&pti->ptLast, &pMsg->pt, sizeof(POINT)))
              {
                 pti->TIF_flags |= TIF_MSGPOSCHANGED;
              }
              pti->timeLast = pMsg->time;
              pti->ptLast   = pMsg->pt;
           }

           /* 调用 WH_GETMESSAGE 钩子 */
           co_HOOK_CallHooks(WH_GETMESSAGE, HC_ACTION, RemoveMsg & PM_REMOVE, (LPARAM)pMsg);

           if (bGMSG || pMsg->message == WM_PAINT) break;
        }

        if (bGMSG)
        {
            /* GetMessage 阻塞模式:等待新消息 */
            Status = co_MsqWaitForNewMessages(pti,
                                              Window,
                                              MsgFilterMin,
                                              MsgFilterMax);
            if (!NT_SUCCESS(Status) ||
                Status == STATUS_USER_APC ||
                Status == STATUS_TIMEOUT)
            {
                Present = -1;
                break;
            }
        }
        else
        {
           /* PeekMessage 非阻塞:让出 CPU */
           if (!(RemoveMsg & PM_NOYIELD))
           {
              IdlePing();
              UserLeave();
              ZwYieldExecution();
              UserEnterExclusive();
              IdlePong();
           }
           break;
        }
    }
    while (bGMSG && !Present);

    return Present;
}

关键点

  1. HWND_TOPMOST/BROADCAST 转换:两者都视为 HWND_BOTTOM(任意窗口);
  2. DDE 消息特殊处理:DDE 消息需要额外的 hook;
  3. TIF_MSGPOSCHANGED:标记消息位置已变(GetCursorPos 缓存失效);
  4. WH_GETMESSAGE hook:应用可监控所有 GetMessage/PeekMessage 返回的消息;
  5. GetMessage 阻塞:调用 co_MsqWaitForNewMessages 等待;
  6. PeekMessage 让出:调用 ZwYieldExecution 让出 CPU。

7.2.4 co_IntPeekMessage 调度核心

7.2.4.1 函数实现

win32ss/user/ntuser/message.c:1013(file:///d:/reactos/win32ss/user/ntuser/message.c#L1013):

c 复制代码
BOOL APIENTRY
co_IntPeekMessage(PMSG Msg,
                  PWND Window,
                  UINT MsgFilterMin,
                  UINT MsgFilterMax,
                  UINT RemoveMsg,
                  LONG_PTR *ExtraInfo,
                  BOOL bGMSG)
{
    PTHREADINFO pti;
    BOOL RemoveMessages;
    UINT ProcessMask;
    BOOL Hit = FALSE;

    pti = PsGetCurrentThreadWin32Thread();

    RemoveMessages = RemoveMsg & PM_REMOVE;
    ProcessMask = HIWORD(RemoveMsg);

    /* 默认 ProcessMask */
    if (!ProcessMask) ProcessMask = (QS_ALLPOSTMESSAGE | QS_ALLINPUT);

    IdlePong();

    do
    {
        /* 更新最后访问时间 */
        pti->pcti->timeLastRead = EngGetTickCount32();

        /* 在循环中分派鼠标移动 */
        if (pti->MessageQueue->QF_flags & QF_MOUSEMOVED)
        {
           IntCoalesceMouseMove(pti);
        }

        /* 第一遍:分派发送消息 */
        while (co_MsqDispatchOneSentMessage(pti))
        {
           if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE;
        }
        if (Hit) return FALSE;

        /* 清除 change bits */
        if (ProcessMask & QS_POSTMESSAGE)
        {
           pti->pcti->fsChangeBits &= ~(QS_POSTMESSAGE | QS_HOTKEY | QS_TIMER);
           if (MsgFilterMin == 0 && MsgFilterMax == 0)
           {
              pti->pcti->fsChangeBits &= ~QS_ALLPOSTMESSAGE;
           }
        }
        if (ProcessMask & QS_INPUT)
        {
           pti->pcti->fsChangeBits &= ~QS_INPUT;
        }

        /* 检查投递消息 */
        if ((ProcessMask & QS_POSTMESSAGE || ProcessMask & QS_HOTKEY) &&
            MsqPeekMessage(pti,
                            RemoveMessages,
                            Window,
                            MsgFilterMin,
                            MsgFilterMax,
                            ProcessMask,
                            ExtraInfo,
                            0,
                            Msg))
        {
            goto GotMessage;
        }

        /* WM_QUIT 消息 */
        if (ProcessMask & QS_POSTMESSAGE && pti->QuitPosted)
        {
            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;
            }
            goto GotMessage;
        }

        /* 硬件消息 */
        if ((ProcessMask & QS_INPUT) &&
            co_MsqPeekHardwareMessage(pti,
                                      RemoveMessages,
                                      Window,
                                      MsgFilterMin,
                                      MsgFilterMax,
                                      ProcessMask,
                                      Msg))
        {
            goto GotMessage;
        }

        /* 内部事件 */
        {
           LONG_PTR eExtraInfo;
           MSG eMsg;
           DWORD dwQEvent;
           if (MsqPeekMessage(pti,
                               TRUE,
                               Window,
                               0, 0,
                               QS_EVENT,
                              &eExtraInfo,
                              &dwQEvent,
                              &eMsg))
           {
              handle_internal_events(pti, Window, dwQEvent, eExtraInfo, &eMsg);
              continue;
           }
        }

        /* 第二遍:分派发送消息(避免长时阻塞) */
        while (co_MsqDispatchOneSentMessage(pti))
        {
           if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE;
        }
        if (Hit) return FALSE;

        /* WM_PAINT 消息 */
        if ((ProcessMask & QS_PAINT) &&
            pti->cPaintsReady &&
            IntGetPaintMessage(Window, MsgFilterMin, MsgFilterMax, pti, Msg, RemoveMessages))
        {
            goto GotMessage;
        }

        /* 定时器消息 */
        if ((ProcessMask & QS_TIMER) &&
            PostTimerMessages(Window))
        {
            continue;
        }

        return FALSE;
    }
    while (TRUE);

GotMessage:
    pti->pcti->timeLastRead = EngGetTickCount32();
    return TRUE;
}

7.2.4.2 7 步扫描流程

整个 PeekMessage 实现遵循 MSDN 规定的优先级顺序

复制代码
┌──────────────────────────────────────────────────────────────────┐
│  PeekMessage 扫描流程                                              │
├──────────────────────────────────────────────────────────────────┤
│  1. 发送消息 (Sent)         ── co_MsqDispatchOneSentMessage     │
│  2. 投递消息 (Posted)       ── MsqPeekMessage                   │
│  3. WM_QUIT                 ── 特殊处理                         │
│  4. 硬件消息 (Hardware)     ── co_MsqPeekHardwareMessage        │
│  5. 内部事件 (Internal)     ── handle_internal_events           │
│  6. 发送消息 (Sent) 再次    ── co_MsqDispatchOneSentMessage     │
│  7. 绘制消息 (Paint)        ── IntGetPaintMessage               │
│  8. 定时器消息 (Timer)      ── PostTimerMessages                │
│  ────────────────────────────────────────────────────────────── │
│  全部无消息时:返回 FALSE                                         │
└──────────────────────────────────────────────────────────────────┘

7.2.4.3 发送消息的两遍扫描

为什么需要两遍扫描发送消息?

  • 第一遍(在投递消息之前):处理已经到达的发送消息(避免延迟);
  • 第二遍(在硬件消息之后):处理在硬件消息处理过程中新到达的发送消息(避免死锁)。

这种设计是为了防止线程 A 在处理硬件消息时,线程 B 调用 SendMessage 给 A 同步等待,造成死锁。


7.2.5 co_MsqPeekHardwareMessage 硬件消息

7.2.5.1 函数实现

win32ss/user/ntuser/msgqueue.c:1938(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1938):

c 复制代码
BOOL APIENTRY
co_MsqPeekHardwareMessage(IN PTHREADINFO pti,
                          IN BOOL Remove,
                          IN PWND Window,
                          IN UINT MsgFilterLow,
                          IN UINT MsgFilterHigh,
                          IN UINT QSflags,
                          OUT MSG* pMsg)
{
   PUSER_MESSAGE CurrentMessage;
   PLIST_ENTRY ListHead;
   DWORD QS_Flags;
   BOOL Ret = FALSE;

   ListHead = pti->MessageQueue->HardwareMessagesListHead.Flink;

   if (IsListEmpty(ListHead)) return FALSE;

   while (ListHead != &pti->MessageQueue->HardwareMessagesListHead)
   {
      CurrentMessage = CONTAINING_RECORD(ListHead, USER_MESSAGE, ListEntry);
      ListHead = ListHead->Flink;

      /* 检查窗口过滤 */
      if ((!Window || Window == PWND_BOTTOM || UserHMGetHandle(Window) == CurrentMessage->Msg.hwnd) &&
          /* 检查消息范围过滤 */
          ((MsgFilterLow == 0 && MsgFilterHigh == 0 && (CurrentMessage->QS_Flags & QSflags)) ||
           (MsgFilterLow <= CurrentMessage->Msg.message && MsgFilterHigh >= CurrentMessage->Msg.message)))
      {
         *pMsg = CurrentMessage->Msg;
         QS_Flags = CurrentMessage->QS_Flags;
         if (Remove)
         {
             if (CurrentMessage->pti != NULL)
             {
                MsqDestroyMessage(CurrentMessage);
             }
             ClearMsgBitsMask(pti, QS_Flags);
         }
         Ret = TRUE;
         break;
      }
   }
   return Ret;
}

7.2.5.2 鼠标消息特殊处理

注意:硬件消息的 QS_Flags 可以是 QS_MOUSEMOVEQS_MOUSEBUTTONQS_KEY。但当鼠标消息被检查时,会通过 co_IntProcessMouseMessage 进行额外处理(路由到正确窗口、双击判定等)。


7.2.6 co_IntProcessMouseMessage 鼠标消息路由

7.2.6.1 函数概述

win32ss/user/ntuser/msgqueue.c:1472(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1472):

co_IntProcessMouseMessage 是鼠标消息的"路由与转换"中心,负责:

  • 找到正确目标窗口(捕获/Hit-Test)
  • 区分客户区/非客户区
  • 双击判定
  • 鼠标单击锁定(ClickLock)

7.2.6.2 窗口路由

c 复制代码
/* find the window to dispatch this mouse message to */
if (MessageQueue->spwndCapture)
{
    hittest = HTCLIENT;
    pwndMsg = MessageQueue->spwndCapture;
}
else
{
    pwndMsg = co_WinPosWindowFromPoint(NULL, &msg->pt, &hittest, FALSE);
}

优先级

  1. 捕获窗口spwndCapture)------ SetCapture 强制所有鼠标消息路由到指定窗口;
  2. Hit-Test 命中窗口co_WinPosWindowFromPoint)------ 鼠标位置命中的窗口。

7.2.6.3 客户区/非客户区转换

c 复制代码
if (hittest != HTCLIENT)
{
    message += WM_NCMOUSEMOVE - WM_MOUSEMOVE;
    msg->wParam = hittest;
}
else
{
    if (!(MessageQueue->MenuOwner))
    {
        pt.x += pwndDesktop->rcClient.left - pwndMsg->rcClient.left;
        pt.y += pwndDesktop->rcClient.top - pwndMsg->rcClient.top;
    }
}
msg->lParam = MAKELONG(pt.x, pt.y);
  • 命中客户区 (HTCLIENT):发送 WM_MOUSEMOVE,坐标为窗口客户区局部坐标;
  • 命中非客户区 (HTBORDER、HTCAPTION 等):发送 WM_NCMOUSEMOVE,wParam 为 hit-test 代码。

7.2.6.4 双击判定

c 复制代码
if ((msg->message == WM_LBUTTONDOWN) ||
    (msg->message == WM_RBUTTONDOWN) ||
    (msg->message == WM_MBUTTONDOWN) ||
    (msg->message == WM_XBUTTONDOWN))
{
    /* translate double-clicks */
    if ((MessageQueue->MenuOwner || MessageQueue->MoveSize) ||
        hittest != HTCLIENT ||
        (pwndMsg->pcls->style & CS_DBLCLKS))
    {
       if ((msg->message == clk_msg.message) &&
           (msg->hwnd == clk_msg.hwnd) &&
           ((msg->time - clk_msg.time) < (ULONG)gspv.iDblClickTime) &&
           (abs(msg->pt.x - clk_msg.pt.x) < UserGetSystemMetrics(SM_CXDOUBLECLK)/2) &&
           (abs(msg->pt.y - clk_msg.pt.y) < UserGetSystemMetrics(SM_CYDOUBLECLK)/2))
       {
           message += (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
           if (update)
           {
               MessageQueue->msgDblClk.message = 0;
               update = FALSE;
           }
       }
    }
    if (update) MessageQueue->msgDblClk = *msg;
}

双击判定条件(必须全部满足):

  1. 窗口类支持双击(CS_DBLCLKS);
  2. 与上次按键消息类型相同clk_msg.message);
  3. 距离上次按键时间 < gspv.iDblClickTime(默认 500ms);
  4. 水平距离 < SM_CXDOUBLECLK/2(默认 4 像素);
  5. 垂直距离 < SM_CYDOUBLECLK/2(默认 4 像素);
  6. 命中客户区(hittest == HTCLIENT)。

7.2.6.5 消息队列切换处理

c 复制代码
if (pwndMsg == NULL || pwndMsg->head.pti->MessageQueue != MessageQueue)
{
    // Crossing a boundary, so set cursor. See default message queue cursor.
    IntSystemSetCursor(SYSTEMCUR(ARROW));
    /* Remove and ignore the message */
    *RemoveMessages = TRUE;
    return FALSE;
}

if (pwndMsg->head.pti != pti && MessageQueue->cThreads > 1)
{
    /* This is not for us and we should leave so the other thread can check for messages */
    *NotForUs = TRUE;
    *RemoveMessages = FALSE;
    return FALSE;
}

跨队列处理

  • 目标窗口属于其他消息队列cThreads > 1 共享队列)时,丢弃消息并切换光标;
  • 目标窗口属于其他线程的独立队列时,标记为 NotForUs,让其他线程处理。

7.2.7 co_IntProcessKeyboardMessage 键盘消息处理

7.2.7.1 函数实现

win32ss/user/ntuser/msgqueue.c:1772(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1772):

c 复制代码
BOOL co_IntProcessKeyboardMessage(MSG* Msg, BOOL* RemoveMessages)
{
    PTHREADINFO pti;
    PUSER_MESSAGE_QUEUE MessageQueue;
    BYTE KeyState, AsyncKeyState;
    UINT message;
    PWND pWnd;
    BOOL EatMsg = FALSE;

    pti = PsGetCurrentThreadWin32Thread();
    MessageQueue = pti->MessageQueue;
    message = Msg->message;

    /* 验证消息范围 */
    if (message < WM_KEYFIRST || message > WM_KEYLAST) return FALSE;

    /* 更新异步按键状态 */
    if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN)
        UpdateKeyStateFromMsg(MessageQueue, Msg);

    /* 处理 IME 热键 */
    if (co_IntImmProcessKey(Msg->hwnd, pti->KeyboardLayout->hkl, ...))
    {
        *RemoveMessages = TRUE;
        return FALSE;
    }

    /* 路由到焦点窗口或活动窗口 */
    pWnd = MessageQueue->spwndFocus;
    if (!pWnd || pWnd->head.pti->MessageQueue != MessageQueue)
    {
        pWnd = MessageQueue->spwndActive;
    }

    if (pWnd && pWnd->head.pti->MessageQueue == MessageQueue)
    {
        Msg->hwnd = UserHMGetHandle(pWnd);
        return TRUE;
    }
    else
    {
        *RemoveMessages = TRUE;
        return FALSE;
    }
}

7.2.7.2 键盘消息路由

优先级

  1. 焦点窗口spwndFocus)------ 通过 SetFocus 设置的键盘接收者;
  2. 活动窗口spwndActive)------ 当前前台窗口;
  3. 如果都不可用,丢弃消息。

7.2.7.3 异步按键状态(AsyncKeyState)

gafAsyncKeyState 是全局异步按键状态数组,256 个键 × 2 bit:

c 复制代码
BYTE gafAsyncKeyState[256 * 2 / 8];  // 64 字节,每键 2 bit
static BYTE gafAsyncKeyStateRecentDown[256 / 8];  // 32 字节,每键 1 bit

每键 2 bit 表示:

  • bit 0:当前是否按下(1=按下);
  • bit 1:是否为切换键(1=CapsLock、NumLock 等)。

GetAsyncKeyState API 直接读取这个数组,不进入消息队列,是真正的"实时"状态。


7.2.8 IntDispatchMessage 分发到 WndProc

7.2.8.1 函数实现

win32ss/user/ntuser/message.c:889(file:///d:/reactos/win32ss/user/ntuser/message.c#L889):

c 复制代码
LRESULT FASTCALL
IntDispatchMessage(PMSG pMsg)
{
    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;
    }

    /* WM_TIMER / WM_SYSTIMER 特殊处理 */
    if (((pMsg->message == WM_SYSTIMER) || (pMsg->message == WM_TIMER)) && (pMsg->lParam))
    {
        if (pMsg->message == WM_TIMER)
        {
            if (ValidateTimerCallback(pti, pMsg->lParam))
            {
                Time = EngGetTickCount32();
                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)
            {
                Time = EngGetTickCount32();
                pTimer->pfn(pMsg->hwnd, WM_SYSTIMER, (UINT)pMsg->wParam, Time);
            }
            return 0;
        }
    }

    if (!Window) return 0;

    if (pMsg->message == WM_PAINT) Window->state |= WNDS_PAINTNOTPROCESSED;

    /* 服务端窗口过程(FNID_DESKTOP 等) */
    if (Window->state & WNDS_SERVERSIDEWINDOWPROC)
    {
       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;
          case FNID_MENU:
            DoCallBack = !PopupMenuWndProc(Window, pMsg->message, pMsg->wParam, pMsg->lParam, &retval);
            break;
       }
    }

    /* 用户态窗口过程 */
    if (DoCallBack)
    retval = co_IntCallWindowProc(Window->lpfnWndProc,
                                  !Window->Unicode,
                                  pMsg->hwnd,
                                  pMsg->message,
                                  pMsg->wParam,
                                  pMsg->lParam,
                                  -1);

    /* WM_PAINT 后续处理 */
    if (pMsg->message == WM_PAINT &&
        VerifyWnd(Window) &&
        Window->state & WNDS_PAINTNOTPROCESSED)
    {
        Window->state2 &= ~WNDS2_WMPAINTSENT;
        IntPaintWindow(Window);
    }

    return retval;
}

7.2.8.2 三种 WndProc 路径

DispatchMessage 根据窗口的 fnid 决定走哪条路径:

FNID 路径 位置
FNID_DESKTOP DesktopWindowProc 内核态
FNID_MENU PopupMenuWndProc 内核态
FNID_MESSAGEWND UserMessageWindowProc 内核态
FNID_SCROLLBAR ScrollBarWndProc 内核态
普通窗口 co_IntCallWindowProc → 用户态 WndProc 用户态

WNDS_SERVERSIDEWINDOWPROC 标志 标识这是 win32k 内部维护的窗口(不需要用户态 WndProc)。

7.2.8.3 co_IntCallWindowProc 关键调用

c 复制代码
retval = co_IntCallWindowProc(Window->lpfnWndProc,
                              !Window->Unicode,
                              pMsg->hwnd,
                              pMsg->message,
                              pMsg->wParam,
                              pMsg->lParam,
                              -1);

参数说明:

  • Window->lpfnWndProc:窗口过程函数指针(用户态);
  • !Window->Unicode:是否是 ANSI 窗口过程;
  • -1:lParam 缓冲区大小(-1 表示不需要复制数据)。

co_IntCallWindowProc 内部通过 KeUserModeCallback 切换到用户态,调用实际的 WndProc 函数(详见 7.3 节)。


7.2.9 co_MsqWaitForNewMessages 等待新消息

7.2.9.1 函数实现

win32ss/user/ntuser/msgqueue.c:2118(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L2118):

c 复制代码
NTSTATUS FASTCALL
co_MsqWaitForNewMessages(PTHREADINFO pti, PWND WndFilter,
                         UINT MsgFilterMin, UINT MsgFilterMax)
{
    NTSTATUS ret = STATUS_SUCCESS;

    /* 在等待前分派合并的鼠标移动 */
    if (pti->MessageQueue->QF_flags & QF_MOUSEMOVED)
    {
        IntCoalesceMouseMove(pti);
    }

    /* 释放用户态锁(允许其他线程进入) */
    UserLeaveCo();

    /* 让出 CPU 片刻 */
    ZwYieldExecution();

    /* 阻塞等待消息队列事件 */
    ret = KeWaitForSingleObject(pti->pEventQueueServer,
                                 UserRequest,
                                 UserMode,
                                 FALSE,
                                 NULL);

    /* 重新获取用户态锁 */
    UserEnterCo();

    /* 处理用户态 APC */
    if (ret == STATUS_USER_APC)
    {
        TRACE("MWFNW User APC\n");
        co_IntDeliverUserAPC();
    }
    return ret;
}

7.2.9.2 等待-唤醒机制

复制代码
GetMessage 流程
     │
     ▼
co_MsqWaitForNewMessages
     │
     ├─► UserLeaveCo() 释放锁
     │
     ├─► ZwYieldExecution() 让出 CPU
     │
     └─► KeWaitForSingleObject(pEventQueueServer)
            │
            │ (线程挂起)
            │
            ▼
         其他线程 PostMessage
            │
            ▼
         MsqPostMessage 内部
            │
            └─► KeSetEvent(pEventQueueServer)
                  │
                  │ (线程唤醒)
                  ▼
               重新进入 GetMessage 循环

关键点

  • pEventQueueServer 是内核态事件对象(在线程创建时分配);
  • MsqPostMessage 投递消息后会 KeSetEvent 唤醒等待线程;
  • UserLeaveCo/UserEnterCo 在等待前后释放/获取用户态锁(允许其他线程进入 win32k)。

7.2.9.3 死锁避免

co_MsqWaitForNewMessages 在等待前会调用 IntCoalesceMouseMove,确保所有合并的鼠标消息在等待前已处理,避免:

  • 线程 A 等待消息;
  • 线程 B 在等待中产生鼠标消息;
  • 鼠标消息被合并但永远不会被处理(因为 A 已挂起)。

7.2.10 设计哲学问答

Q1:为什么 MSDN 规定 PeekMessage 必须按特定顺序扫描消息?

A :这是为了避免死锁保证公平性

  • 发送消息优先级最高:避免 SendMessage 跨线程时死锁;
  • 投递消息其次:异步消息应尽快处理;
  • 硬件消息再次:UI 响应性优先(键盘/鼠标不能延迟);
  • 内部事件:系统通知需要及时处理;
  • 绘制消息:按需触发,优先级最低。

如果颠倒这个顺序,可能会出现:用户移动鼠标但窗口因处理长时发送消息而冻结,或 SendMessage 永远等不到 WndProc 返回(因为接收方在处理其他消息)。

Q2:为什么需要两遍扫描发送消息?

A :避免跨线程 SendMessage 死锁

考虑场景:

  1. 线程 A 正在处理投递消息(来自线程 B 的 PostMessage);
  2. 线程 C 调用 SendMessage 给 A,阻塞等待;
  3. 如果 A 继续处理投递消息,可能触发对 C 的 SendMessage,形成环路死锁。

通过在投递消息前后两遍扫描发送消息,可以在线程 A 处理投递消息时,先把 C 的发送消息处理完,避免 A 反过来给 C 发消息时出现环路。

Q3:为什么 USER_MESSAGE 用 PagedPool 而 USER_MESSAGE_QUEUE 用 NonPagedPool?

A:访问频率与使用环境的差异:

  • USER_MESSAGE:消息对象,频繁分配/释放,PagedPool 提供更高的内存效率;
  • USER_MESSAGE_QUEUE:队列对象,持有焦点、捕获、激活窗口等关键指针,必须始终驻留内存(NonPagedPool);
  • USER_SENT_MESSAGE:发送消息,持有 KEVENT 同步对象,需要 NonPagedPool。

这种区分是为了在性能和安全之间取得平衡。

Q4:为什么硬件消息使用单独链表(HardwareMessagesListHead)?

A:硬件消息有以下特点需要单独处理:

  1. 多线程共享:键盘和鼠标输入由 RIT(Raw Input Thread)统一接收,然后分派到对应线程;
  2. 高频率:鼠标移动每毫秒可达数百次,单独链表避免影响投递消息;
  3. 特殊处理 :需要经过 co_MsqPeekHardwareMessage 路由到正确窗口;
  4. 优先级可独立控制 :可以单独等待硬件消息(GetMessage(..., WM_MOUSEFIRST, WM_MOUSELAST))。

Q5:为什么 WM_PAINT 消息在所有消息之后处理?

A :绘制是累积式的,频繁重绘会浪费 CPU:

  • 窗口失效时只设置 WNDS_PAINTNOTPROCESSED 标志;
  • 当消息队列空闲时才真正合成 WM_PAINT 消息;
  • 应用程序必须显式处理 WM_PAINT(BeginPaint/EndPaint)。

这种设计保证了:

  • 多次失效只产生一次 WM_PAINT;
  • 处理其他消息时不会被绘制消息打断;
  • 应用程序可以在合适的时机(如 CPU 空闲)才进行重绘。

Q6:为什么 GetMessage 在没有消息时必须阻塞线程?

A :避免CPU 100% 占用

如果 GetMessage 在无消息时立即返回 FALSE,应用程序的消息循环将变成:

c 复制代码
while (GetMessage(&msg, NULL, 0, 0)) {  // 永远不阻塞
    DispatchMessage(&msg);
}
// CPU 100% 占用

阻塞等待可以让线程在无消息时挂起,直到 MsqPostMessage 通过 KeSetEvent 唤醒,从而实现事件驱动的节能模式。


总结

视窗报文的接收是 win32k 中最复杂、最高频的操作之一。本节介绍了:

  1. 5 种消息源:Sent、Posted、Hardware、Internal、Paint、Timer;
  2. 消息优先级:MSDN 规定的 7 步扫描顺序;
  3. GetMessage/PeekMessage:阻塞/非阻塞两种模式;
  4. co_IntPeekMessage:核心调度函数,按优先级扫描所有消息源;
  5. 鼠标/键盘路由:捕获、焦点、Hit-Test、双击判定;
  6. DispatchMessage:分发到服务端 WndProc 或用户态 WndProc;
  7. 等待-唤醒机制:KeSetEvent/KeWaitForSingleObject。

核心要点回顾

  1. GetMessage 严格遵循 MSDN 7 步优先级扫描;
  2. 发送消息需要两遍扫描以避免死锁;
  3. 硬件消息有独立的链表和路由逻辑;
  4. WM_PAINT 是累积式绘制,按需合成;
  5. GetMessage 阻塞通过事件对象实现,避免 CPU 占用。

本章代码索引

文件 内容
win32ss/user/ntuser/message.c(file:///d:/reactos/win32ss/user/ntuser/message.c) NtUserGetMessage、co_IntGetPeekMessage、co_IntPeekMessage、IntDispatchMessage
win32ss/user/ntuser/msgqueue.c(file:///d:/reactos/win32ss/user/ntuser/msgqueue.c) MsqPeekMessage、co_MsqPeekHardwareMessage、co_MsqDispatchOneSentMessage、co_MsqWaitForNewMessages、co_IntProcessMouseMessage、co_IntProcessKeyboardMessage
win32ss/user/ntuser/msgqueue.h(file:///d:/reactos/win32ss/user/ntuser/msgqueue.h) USER_MESSAGE、USER_SENT_MESSAGE、USER_MESSAGE_QUEUE、QS_、SMF_、QF_* 标志
win32ss/user/ntuser/callback.c(file:///d:/reactos/win32ss/user/ntuser/callback.c) co_IntCallWindowProc、回调机制(详见 7.3)
win32ss/user/ntuser/ntstubs.c(file:///d:/reactos/win32ss/user/ntuser/ntstubs.c) NtUserRealInternalGetMessage(stub)
win32ss/user/user32/windows/message.c(file:///d:/reactos/win32ss/user/user32/windows/message.c) user32 侧 GetMessage/DispatchMessage 包装
相关推荐
caimouse2 小时前
Reactos 第 8 章 结构化异常处理 — 8.3 用户空间的结构化异常处理
windows
caimouse2 小时前
Reactos 第 9 章 设备驱动 — 9.6 中断处理
网络·windows
caimouse2 小时前
Reactos 第 7 章 视窗报文 — 7.6 键盘输入线程
windows
yinhunzw3 小时前
Claude code windows 安装
windows
七仔啊3 小时前
windows server 2022 部署前后端项目
windows
caimouse3 小时前
Reactos 第 7 章 视窗报文 — 7.4 用户空间的外挂函数
windows
辣香牛肉面4 小时前
Windows发票工具大全
windows·发票助手
caimouse4 小时前
Reactos 第 9 章 设备驱动 — 9.3 DPC函数及其执行
windows
caimouse5 小时前
Reactos 第 9 章 设备驱动 — 9.8 设备驱动模块的装载
windows