欢迎光临
我们一直在努力

Reactos 第 7 章 视窗报文 — 7.5 视窗报文的发送

第 7 章 视窗报文 — 7.5 视窗报文的发送

本节深入剖析 Windows/ReactOS 中"视窗报文发送"(Send/Post/Notify)的内核态实现,包括同步发送、异步发送、超时控制、跨进程边界检查、死锁检测、广播、ReplyMessage 等核心机制。

概述

发送消息是 Windows GUI 程序间通信的核心机制之一。与"投递消息"(Post)不同,发送(Send)意味着"调用者等待接收方处理完消息并返回结果"——这是一种同步、阻塞、可获得返回值的通信方式。ReactOS 的视窗报文发送系统需要解决以下核心问题:

  • 同线程 fast path:当接收方是当前线程的窗口时,直接调用 WndProc,避免线程间同步;
  • 跨线程同步等待:当接收方是其他线程时,需要等待事件 + 线程上下文切换;
  • 超时与死锁检测:避免发送方无限期等待死锁的接收方;
  • 跨进程边界检查:防止敏感消息(密码、WM_CREATE)跨进程泄露;
  • 接收方/发送方死亡处理:在对方线程意外终止时清理资源;
  • 广播:特殊消息(HWND_BROADCAST)遍历所有顶层窗口。

发送消息的本质是什么?
发送消息是一种"同步 RPC"(远程过程调用):发送方线程在 Win32k 内核态挂起,直到接收方线程的 WndProc 处理完消息并通过 ReplyMessage 通知结果。Windows 通过 USER_SENT_MESSAGE 结构 + 事件对象(pkCompletionEvent)+ 等待队列(KeWaitForMultipleObjects)实现这一机制。

想象一个"内部传话"场景:

  • SendMessage:员工 A 写一封信给员工 B,站在 B 办公桌前等他回信(同步阻塞);
  • PostMessage:员工 A 把信投入 B 的信箱,继续做自己的工作(异步非阻塞);
  • SendMessageCallback:员工 A 把信投入 B 的信箱,附上一张"请回信到 5 号邮箱"的便条,回信到达 5 号邮箱时由 A 的助理代为处理;
  • SendNotifyMessage:与 SendMessage 类似,但指针参数是复制过去的(适合带数据的消息);
  • HWND_BROADCAST:员工 A 把信投入公司所有员工的信箱(遍历所有顶层窗口);
  • ReplyMessage:员工 B 把回信写好并交给信使(设置 QS_SMRESULT 标志)。

本节内容概览

  • 7.5.0 框架图:视窗报文发送的整体架构;
  • 7.5.1 视窗报文发送总览:4 种语义(Send/Post/SendCallback/Notify);
  • 7.5.2 USER_SENT_MESSAGE 与 SMF_ 状态标志*;
  • 7.5.3 SendMessage 同线程 fast path;
  • 7.5.4 co_IntSendMessageTimeoutSingle 跨线程发送详解;
  • 7.5.5 跨进程边界与 DDE 检查;
  • 7.5.6 死锁检测与超时机制;
  • 7.5.7 co_MsqSendMessage 消息队列同步等待;
  • 7.5.8 接收者死亡与发送者死亡;
  • 7.5.9 co_MsqSendMessageAsync 异步发送;
  • 7.5.10 广播、Notify 与 SendMessageCallback;
  • 7.5.11 co_MsqReplyMessage 同步响应;
  • 7.5.12 设计哲学问答:5 个关键设计问题解答。
  • 学习目标

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

    • 理解视窗报文发送的 4 种语义(Send/Post/SendCallback/Notify);
    • 掌握 USER_SENT_MESSAGE 结构与 SMF_* 状态标志;
    • 分析 SendMessage 同线程 fast path 与跨线程同步路径;
    • 理解跨进程边界检查(DDE 密码、WM_CREATE)的必要性;
    • 掌握死锁检测(MsqIsHung)与超时机制;
    • 解释接收者/发送者死亡时的资源清理流程;
    • 掌握 co_MsqReplyMessage 的同步响应机制。

    涉及的内核子系统

    子系统职责
    win32k.sys/ntuser/message.c co_IntSendMessage 家族、UserSendNotifyMessage
    win32k.sys/ntuser/msgqueue.c co_MsqSendMessage、co_MsqSendMessageAsync、co_MsqReplyMessage
    win32k.sys/ntuser/msgqueue.h USER_SENT_MESSAGE 结构、SMF_* 标志、QS_SMRESULT
    ntoskrnl/ke KeWaitForMultipleObjects、KeSetEvent 同步原语
    user32.dll SendMessage 用户态入口(user32/windows/message.c)

    7.5.0 框架图

    ┌──────────────────────────────────────────────────────────────────────────────────────┐
    │ 视窗报文发送(Send/Post/Notify)整体架构 │
    ├──────────────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ 用户态 API │
    │ ┌────────────────────────────────────────────────────────────────────────────┐ │
    │ │ user32.dll SendMessage / SendMessageTimeout / SendMessageCallback │ │
    │ │ SendNotifyMessage / PostMessage / PostThreadMessage │ │
    │ │ ReplyMessage │ │
    │ └────────────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ (syscall) │
    │ 内核态 win32k │
    │ ┌────────────────────────────────────────────────────────────────────────────┐ │
    │ │ message.c │ │
    │ │ ├─► co_IntSendMessage ─┐ │ │
    │ │ ├─► co_IntSendMessageTimeoutSingle │ 4 种 Send 变体 │ │
    │ │ ├─► co_IntSendMessageNoWait ─┤ │ │
    │ │ ├─► co_IntSendMessageWithCallBack ─┘ │ │
    │ │ └─► UserSendNotifyMessage │ │
    │ │ │ │ │
    │ │ ▼ │ │
    │ │ msgqueue.c │ │
    │ │ ├─► co_MsqSendMessage (同步等待内核态核心) │ │
    │ │ ├─► co_MsqSendMessageAsync (异步发送) │ │
    │ │ ├─► co_MsqDispatchOneSentMessage (接收方分派) │ │
    │ │ └─► co_MsqReplyMessage (接收方响应) │ │
    │ │ │ │ │
    │ │ ▼ │ │
    │ │ ntoskrnl │ │
    │ │ └─► KeWaitForMultipleObjects (跨线程同步等待) │ │
    │ │ KeSetEvent / KeUserModeCallback │ │
    │ └────────────────────────────────────────────────────────────────────────────┘ │
    │ │
    │ 4 种发送语义对比 │
    │ ┌────────────────────────────────────────────────────────────────────────────┐ │
    │ │ 类型 阻塞 跨线程 返回值 lParam 指针 完成通知 │ │
    │ │ ───────────────────────────────────────────────────────────────── │ │
    │ │ SendMessage Yes Yes Yes 不允许跨进程 pkCompletionEvent │ │
    │ │ SendNotify No Yes No 复制 不需要 │ │
    │ │ SendMessageCb No Yes No 不允许跨进程 回调函数 │ │
    │ │ PostMessage No Yes No 不允许跨进程 不需要 │ │
    │ └────────────────────────────────────────────────────────────────────────────┘ │
    │ │
    │ 同步发送核心数据结构 │
    │ ┌────────────────────────────────────────────────────────────────────────────┐ │
    │ │ USER_SENT_MESSAGE │ │
    │ │ ├─► Msg (hwnd, message, wParam, lParam) 消息内容 │ │
    │ │ ├─► ptiReceiver / ptiSender 收发双方线程 │ │
    │ │ ├─► ptiCallBackSender 回调发送者 │ │
    │ │ ├─► pkCompletionEvent 同步事件(信号量) │ │
    │ │ ├─► CompletionCallback 异步回调函数 │ │
    │ │ ├─► HookMessage 钩子消息标记 │ │
    │ │ ├─► HasPackedLParam lParam 是否为打包的指针 │ │
    │ │ ├─► QS_Flags = QS_SENDMESSAGE | QS_SMRESULT │ │
    │ │ └─► flags = SMF_RECEIVERFREE | SMF_RECEIVERBUSY | SMF_RECEIVERDIED │ │
    │ │ | SMF_SENDERDIED │ │
    │ └────────────────────────────────────────────────────────────────────────────┘ │
    │ │
    └──────────────────────────────────────────────────────────────────────────────────────┘


    7.5.1 视窗报文发送总览:4 种语义

    7.5.1.1 4 种发送 API 对比

    API阻塞跨线程返回值lParam 指针完成机制内核函数
    SendMessage 同步阻塞 Yes Yes 不可跨进程(自动复制) pkCompletionEvent co_IntSendMessage
    SendMessageTimeout 同步超时 Yes Yes 不可跨进程 pkCompletionEvent + Timeout co_IntSendMessageTimeout
    SendMessageCallback 异步非阻塞 Yes 间接(回调) 不可跨进程 回调函数 co_IntSendMessageWithCallBack
    SendNotifyMessage 异步非阻塞 Yes No 自动复制 不需要 co_IntSendMessageNoWait
    PostMessage 异步非阻塞 Yes No 不可跨进程 不需要 MsqPostMessage

    7.5.1.2 同步 vs 异步语义

    同步发送(SendMessage):

    线程 A Win32k 内核 线程 B
    │ │ │
    ├──NtUserSendMessage───────►│ │
    │ (等待 pkEvent) ├─插入 SentMessagesList►│
    │ │ ├──GetMessage 取消息
    │ │ ├──WndProc 处理
    │ BLOCKED │ ├──ReplyMessage
    │ │◄─────────结果──────────┤
    │◄─── KeWaitForSingleObject─┤ (Event 触发) │
    │ │ │
    │ 返回处理结果 │ │

    异步发送(SendNotifyMessage):

    线程 A Win32k 内核 线程 B
    │ │ │
    ├──NtUserSendNotifyMessage─►│ │
    │ (复制 lParam 数据) ├─插入 SentMessagesList►│
    │ │ ├──GetMessage 取消息
    │◄─── 立即返回 ─────────────┤ ├──WndProc 处理
    │ │ │ (无返回值)

    7.5.1.3 同步 vs 异步的本质差异

    维度同步发送异步发送
    发送方状态 在内核态阻塞(KeWaitForMultipleObjects) 立即返回用户态
    线程调度 发送方不消耗 CPU,但占线程栈 发送方继续运行
    结果传递 通过共享 USER_SENT_MESSAGE.lResult 通过回调函数(callback)或无
    死锁风险 高(必须检测) 无(不会等待)
    用途 状态查询、配置、强制同步 通知、状态变化通知

    7.5.2 USER_SENT_MESSAGE 与 SMF_* 状态标志

    7.5.2.1 USER_SENT_MESSAGE 结构

    [win32ss/user/ntuser/msgqueue.h](file:///d:/reactos/win32ss/user/ntuser/msgqueue.h) 定义:

    typedef struct _USER_SENT_MESSAGE
    {
    LIST_ENTRY ListEntry; // 链表节点(SentMessagesListHead)
    MSG Msg; // 消息内容
    PTHREADINFO ptiReceiver; // 接收方线程
    PTHREADINFO ptiSender; // 发送方线程(同步等待时使用)
    PTHREADINFO ptiCallBackSender; // 回调发送方(异步回调时使用)
    PKEVENT pkCompletionEvent; // 完成事件(同步发送用)
    SENDASYNCPROC CompletionCallback; // 完成回调(异步回调用)
    ULONG_PTR CompletionCallbackContext; // 回调上下文
    LRESULT lResult; // 消息处理结果
    ULONG_PTR QS_Flags; // QS_SENDMESSAGE | QS_SMRESULT
    DWORD flags; // SMF_* 状态位
    INT HookMessage; // MSQ_NORMAL / MSQ_ISHOOK / MSQ_INJECTMODULE
    BOOLEAN HasPackedLParam; // lParam 是否为内核态打包的指针
    } USER_SENT_MESSAGE, *PUSER_SENT_MESSAGE;

    7.5.2.2 SMF_* 状态标志

    #define SMF_RECEIVERFREE 0x00000001 // 接收方负责释放(默认 sender 释放)
    #define SMF_RECEIVERBUSY 0x00000002 // 消息正在被处理
    #define SMF_RECEIVERDIED 0x00000004 // 接收方线程死亡
    #define SMF_SENDERDIED 0x00000008 // 发送方线程死亡
    #define SMF_RECEIVEDMESSAGE 0x00000010 // 消息已被接收

    状态转换:

    初始: flags = SMF_RECEIVERFREE
    分派: flags |= SMF_RECEIVERBUSY | SMF_RECEIVEDMESSAGE
    处理完成: flags &= ~SMF_RECEIVERBUSY
    接收方死亡: flags |= SMF_RECEIVERDIED
    发送方死亡: flags |= SMF_SENDERDIED

    7.5.2.3 QS_* 状态位

    #define QS_SENDMESSAGE 0x00000040 // 有 sent message 待处理
    #define QS_SMRESULT 0x00000080 // SendMessage 已完成(结果可用)

    QS_SENDMESSAGE 在 co_MsqSendMessage 中由发送方设置在接收方的 fsWakeBits 上。
    QS_SMRESULT 在 co_MsqReplyMessage 中由接收方设置在 USER_SENT_MESSAGE.QS_Flags 上。


    7.5.3 SendMessage 同线程 fast path

    7.5.3.1 何时走 fast path?

    当 IntSendTo(Window, pti, Msg) 返回 NULL(即接收方就是当前线程)时,win32k 走同线程 fast path——直接调用 co_IntCallWindowProc,不经过 co_MsqSendMessage 同步等待。

    7.5.3.2 关键代码

    [win32ss/user/ntuser/message.c:1550-1641](file:///d:/reactos/win32ss/user/ntuser/message.c#L1550-L1641) co_IntSendMessageTimeoutSingle:

    if ( !ptiSendTo )
    {
    // … 处理 TIF_INCLEANUP …

    if (Msg & 0x80000000)
    {
    // 内部消息(WM_USER 以上)由 win32k 直接处理
    Result = (ULONG_PTR)handle_internal_message( Window, Msg, wParam, lParam );
    // …
    }

    // 1. 触发 WH_CALLWNDPROC 钩子
    IntCallWndProc( Window, hWnd, Msg, wParam, lParam);

    // 2. Server-side window proc (FNID_DESKTOP / FNID_MESSAGEWND / FNID_MENU)
    if ( Window->state & WNDS_SERVERSIDEWINDOWPROC )
    {
    switch(Window->fnid)
    {
    case FNID_DESKTOP:
    DoCallBack = !DesktopWindowProc(...);
    break;
    case FNID_MESSAGEWND:
    DoCallBack = !UserMessageWindowProc(...);
    break;
    case FNID_MENU:
    DoCallBack = !PopupMenuWndProc(...);
    break;
    }
    }

    // 3. 计算 lParam 缓冲区大小
    MsgMemoryEntry = FindMsgMemory(Msg);
    if (NULL == MsgMemoryEntry)
    lParamBufferSize = 1;
    else
    lParamBufferSize = MsgMemorySize(MsgMemoryEntry, wParam, lParam);

    // 4. 打包 lParam 指针
    if (! NT_SUCCESS(PackParam(&lParamPacked, Msg, wParam, lParam, FALSE)))
    goto Cleanup;

    // 5. 调用 WndProc(同线程直接调用,无需 KeUserModeCallback 跨边界!)
    Result = (ULONG_PTR)co_IntCallWindowProc( Window->lpfnWndProc,
    !Window->Unicode,
    hWnd, Msg, wParam, lParamPacked,
    lParamBufferSize );

    // 6. 解包 lParam 指针
    if (! NT_SUCCESS(UnpackParam(lParamPacked, Msg, wParam, lParam, FALSE)))
    goto Cleanup;

    // 7. 触发 WH_CALLWNDPROCRET 钩子
    IntCallWndProcRet( Window, hWnd, Msg, wParam, lParam, (LRESULT *)uResult);

    Ret = TRUE;
    goto Cleanup;
    }

    7.5.3.3 fast path 的关键优势

    优势说明
    无线程切换 同一线程,无需 KeWaitForMultipleObjects
    无死锁风险 不可能发生"自己等自己"
    无内存复制 lParam 指针在同线程中天然可访问
    无超时问题 直接调用,立即返回
    性能更高 减少约 1000-10000 个 CPU 周期(无 syscall 进入退出)

    7.5.3.4 PackParam / UnpackParam

    [lParam 打包机制](file:///d:/reactos/win32ss/user/ntuser/message.c#L284-L300):当消息包含指针(如 WM_SETTEXT 的字符串指针、LB_ADDSTRING 的字符串指针)时:

    • PackParam:将用户态指针复制到内核态缓冲区(避免跨进程访问);
    • UnpackParam:处理完毕后写回用户态(用户可读回数据)。
    • 跨线程发送时,必须打包;同线程发送时通常不打包(FALSE)。

    NTSTATUS FASTCALL
    PackParam(LPARAM *lParamPacked, UINT Msg, WPARAM wParam, LPARAM lParam, BOOL Pack)
    {
    if (!Pack) {
    *lParamPacked = lParam; // 同线程:直接传指针
    return STATUS_SUCCESS;
    }
    // 跨线程:从用户态进程地址空间复制到内核态
    // …
    }


    7.5.4 co_IntSendMessageTimeoutSingle 跨线程发送详解

    7.5.4.1 函数签名

    [win32ss/user/ntuser/message.c:1509-1704](file:///d:/reactos/win32ss/user/ntuser/message.c#L1509-L1704):

    static LRESULT FASTCALL
    co_IntSendMessageTimeoutSingle( HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam,
    UINT uFlags, // SMTO_* 标志
    UINT uTimeout, // 超时(毫秒)
    ULONG_PTR *uResult );

    7.5.4.2 完整执行流程

    co_IntSendMessageTimeoutSingle(hWnd, Msg, wParam, lParam, uFlags, uTimeout, uResult)

    ├── 1. 验证窗口句柄 ──► UserGetWindowObject(hWnd)
    │ 失败 ──► return FALSE

    ├── 2. 引用窗口对象 ──► UserRefObjectCo(Window, &Ref)

    ├── 3. 确定接收方线程 ──► ptiSendTo = IntSendTo(Window, Win32Thread, Msg)
    │ │
    │ ├─► NULL(当前线程)──► 走 fast path(7.5.3)
    │ └─► 非 NULL(其他线程)──► 继续下方

    ├── 4. DDE 消息检查
    │ if (Msg in WM_DDE_FIRST..WM_DDE_LAST)
    │ if (!IntDdeSendMessageHook(…))
    │ return FALSE

    ├── 5. 窗口销毁检查
    │ if (Window->state & WNDS_DESTROYED)
    │ return FALSE

    ├── 6. Hung 检测(仅当 SMTO_ABORTIFHUNG)
    │ if ((uFlags & SMTO_ABORTIFHUNG) && MsqIsHung(ptiSendTo, 4*MSQ_HUNG))
    │ return FALSE

    ├── 7. 跨线程发送(核心)
    │ do {
    │ Status = co_MsqSendMessage(ptiSendTo, hWnd, Msg, wParam, lParam,
    │ uTimeout, uFlags & SMTO_BLOCK,
    │ MSQ_NORMAL, uResult);
    │ } while ((Status == STATUS_TIMEOUT) &&
    │ (uFlags & SMTO_NOTIMEOUTIFNOTHUNG) &&
    │ !MsqIsHung(ptiSendTo, MSQ_HUNG));

    ├── 8. 处理超时
    │ if (Status == STATUS_TIMEOUT)
    │ EngSetLastError(ERROR_TIMEOUT)
    │ return FALSE

    ├── 9. 处理错误
    │ else if (!NT_SUCCESS(Status))
    │ SetLastNtError(Status)
    │ return FALSE

    └── 10. 清理
    UserDerefObjectCo(Window)
    return TRUE

    7.5.4.3 SMTO_* 标志

    #define SMTO_NORMAL 0x0000 // 默认:阻塞至处理完
    #define SMTO_BLOCK 0x0001 // 阻塞接收方线程(用于 SendMessageCallback)
    #define SMTO_ABORTIFHUNG 0x0002 // 接收方挂起时立即返回
    #define SMTO_NOTIMEOUTIFNOTHUNG 0x0008 // 未挂起时不超时(重试)

    7.5.4.4 DDE 特殊处理

    if ( Msg >= WM_DDE_FIRST && Msg <= WM_DDE_LAST )
    {
    if (!IntDdeSendMessageHook(Window, Msg, wParam, lParam))
    {
    ERR("Sending Exit DDE 0x%x\\n",Msg);
    goto Cleanup; // Return FALSE
    }
    }

    DDE(动态数据交换)消息在发送前需通过 IntDdeSendMessageHook 检查所有权(DDE 通道是否还存在)。如果 DDE 通道已关闭,发送将被拒绝。


    7.5.5 跨进程边界与 DDE 检查

    7.5.5.1 安全消息边界

    [win32ss/user/ntuser/msgqueue.c:1107-1138](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1107-L1138) co_MsqSendMessage 中的"边境检查"(Border Patrol):

    if ( HookMessage == MSQ_NORMAL )
    {
    pWnd = ValidateHwndNoErr(Wnd);

    // These can not cross International Border lines!
    if ( pti->ppi != ptirec->ppi && pWnd ) // 跨进程
    {
    switch(Msg)
    {
    case EM_GETLINE: // 编辑框获取行(可能含密码)
    case EM_SETPASSWORDCHAR: // 设置密码字符
    case WM_GETTEXT: // 获取文本
    if ( gpsi->atomSysClass[ICLS_EDIT] == pWnd->pcls->atomClassName && // 是编辑框
    pWnd->style & ES_PASSWORD ) // 是密码框
    {
    if (uResult) *uResult = 1;
    ERR("Running across the border without a passport!\\n");
    EngSetLastError(ERROR_ACCESS_DENIED);
    return STATUS_UNSUCCESSFUL;
    }
    break;
    case WM_NOTIFY: // 通知消息(含指针)
    if (uResult) *uResult = 1;
    ERR("Running across the border without a passport!\\n");
    return STATUS_UNSUCCESSFUL;
    }
    }

    // These can not cross State lines!
    if ( Msg == WM_CREATE || Msg == WM_NCCREATE )
    {
    if (uResult) *uResult = 1;
    ERR("Can not tell the other State we have Create!\\n");
    return STATUS_UNSUCCESSFUL;
    }
    }

    7.5.5.2 三类安全消息

    消息类型检查原因失败处理
    密码/编辑框文本(EM_GETLINE / EM_SETPASSWORDCHAR / WM_GETTEXT) 防止跨进程读取密码 返回 ERROR_ACCESS_DENIED
    WM_NOTIFY NMHDR 指针无法跨进程 返回 STATUS_UNSUCCESSFUL
    WM_CREATE / WM_NCCREATE CREATESTRUCT 含本进程指针 返回 STATUS_UNSUCCESSFUL

    7.5.5.3 设计意图

    Windows 的设计哲学是:“不同进程不应共享内存指针”。当消息携带指针(字符串、结构)时,跨进程发送可能:

    • 暴露本进程内存布局;
    • 让其他进程读写本进程数据;
    • 导致目标进程崩溃(访问无效地址)。

    通过拒绝上述消息的跨进程发送,系统强制应用程序使用安全的 IPC 机制(如共享内存、命名管道、LPC)。


    7.5.6 死锁检测与超时机制

    7.5.6.1 死锁的产生

    考虑以下场景:

    线程 A: SendMessage(hWndB, WM_USER) // 等待 B 处理
    线程 B: SendMessage(hWndA, WM_USER) // 等待 A 处理

    双方互相等待 → 死锁!

    7.5.6.2 MsqIsHung 挂起检测

    BOOL FASTCALL
    MsqIsHung(PTHREADINFO pti, DWORD dwTimeout)
    {
    // 检查线程最后活动时间距离现在是否超过 dwTimeout
    // 如果超过,认为线程"挂起"(hung)
    DWORD LastActivity = pti->pcti->tickLast;
    DWORD Now = EngGetTickCount();
    return ((Now LastActivity) > dwTimeout);
    }

    MSQ_HUNG 常量:[win32ss/user/ntuser/msgqueue.h](file:///d:/reactos/win32ss/user/ntuser/msgqueue.h) 中定义(通常 5 秒)。

    7.5.6.3 SMTO_ABORTIFHUNG 处理

    if ((uFlags & SMTO_ABORTIFHUNG) && MsqIsHung(ptiSendTo, 4 * MSQ_HUNG))
    {
    // FIXME: Set window hung and add to a list.
    ERR("Window %p (%p) (pti %p) is hung!\\n", hWnd, Window, ptiSendTo);
    goto Cleanup; // Return FALSE
    }

    当 SMTO_ABORTIFHUNG 设置且接收方被检测为挂起(4 倍 MSQ_HUNG 时间)时,立即返回而不等待。

    7.5.6.4 SMTO_NOTIMEOUTIFNOTHUNG 重试

    do
    {
    Status = co_MsqSendMessage(ptiSendTo, hWnd, Msg, wParam, lParam,
    uTimeout, (uFlags & SMTO_BLOCK),
    MSQ_NORMAL, uResult);
    }
    while ((Status == STATUS_TIMEOUT) &&
    (uFlags & SMTO_NOTIMEOUTIFNOTHUNG) &&
    !MsqIsHung(ptiSendTo, MSQ_HUNG));

    当 SMTO_NOTIMEOUTIFNOTHUNG 设置时:

    • 如果超时但接收方未挂起(仍在响应),无限重试;
    • 如果接收方已挂起,则停止重试并返回超时。

    典型应用:消息循环在 PeekMessage 中重发消息时使用此标志。

    7.5.6.5 幽灵窗口

    if (0 && MsqIsHung(ptiSendTo, MSQ_HUNG))
    {
    TRACE("Let's go Ghost!\\n");
    IntMakeHungWindowGhosted(hWnd);
    }

    当接收方挂起时,Windows 会将窗口标记为"幽灵窗口"(ghost window)—— 渲染时显示为灰色、不可交互。此代码段在 ReactOS 中被禁用(if (0 && …))。


    7.5.7 co_MsqSendMessage 消息队列同步等待

    7.5.7.1 函数签名

    [win32ss/user/ntuser/msgqueue.c:1055-1334](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1055-L1334):

    NTSTATUS FASTCALL
    co_MsqSendMessage( PTHREADINFO ptirec, // 接收方线程
    HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam,
    UINT uTimeout, BOOL Block, INT HookMessage,
    ULONG_PTR *uResult);

    7.5.7.2 完整执行流程

    co_MsqSendMessage(ptirec, Wnd, Msg, wParam, lParam, uTimeout, Block, HookMessage, uResult)

    ├── 1. 死亡检查
    │ if (pti->TIF_flags & TIF_INCLEANUP || ptirec->TIF_flags & TIF_INCLEANUP)
    │ {
    │ if (pti->TIF_flags & TIF_INCLEANUP && !(ptirec->TIF_flags & TIF_INCLEANUP))
    │ co_MsqSendMessageAsync(…) // 异步发送(fire-and-forget)
    │ return STATUS_UNSUCCESSFUL
    │ }

    ├── 2. 挂起检查
    │ if (IsThreadSuspended(ptirec)) return STATUS_UNSUCCESSFUL

    ├── 3. 跨进程边界检查(见 7.5.5)

    ├── 4. 分配 USER_SENT_MESSAGE
    │ Message = AllocateUserMessage(TRUE) // TRUE = 分配 pkCompletionEvent

    ├── 5. 初始化消息字段
    │ Message->Msg = { Wnd, Msg, wParam, lParam }
    │ Message->ptiReceiver = ptirec
    │ Message->ptiSender = pti
    │ Message->HookMessage = HookMessage
    │ Message->QS_Flags = QS_SENDMESSAGE

    ├── 6. 保存当前消息
    │ SaveMsg = pti->pusmSent
    │ pti->pusmSent = Message

    ├── 7. 插入接收方队列
    │ InsertTailList(&ptirec->SentMessagesListHead, &Message->ListEntry)

    ├── 8. 唤醒接收方
    │ MsqWakeQueue(ptirec, QS_SENDMESSAGE, TRUE)

    ├── 9. 关闭栈交换(首次进入)
    │ if (pti->cEnterCount == 0)
    │ SwapStateEnabled = KeSetKernelStackSwapEnable(FALSE)
    │ pti->cEnterCount++

    ├── 10. 同步等待(核心)
    │ if (Block)
    │ {
    │ // 阻塞模式:只等待 Event + Thread 死亡
    │ WaitObjects[0] = Message->pkCompletionEvent
    │ WaitObjects[1] = ptirec->pEThread
    │ WaitStatus = KeWaitForMultipleObjects(2, …, WaitAny, …)
    │ }
    │ else
    │ {
    │ // 非阻塞模式:处理自己的消息循环
    │ WaitObjects[0] = Message->pkCompletionEvent
    │ WaitObjects[1] = pti->pEventQueueServer // 自己的消息队列
    │ WaitObjects[2] = ptirec->pEThread
    │ do
    │ {
    │ WaitStatus = KeWaitForMultipleObjects(3, …, WaitAny, …)
    │ while (co_MsqDispatchOneSentMessage(pti)) // 处理自己的 sent messages
    │ ;
    │ } while (WaitStatus == STATUS_WAIT_1)
    │ }

    ├── 11. 恢复栈交换(最后退出)
    │ if (–pti->cEnterCount == 0)
    │ KeSetKernelStackSwapEnable(SwapStateEnabled)

    ├── 12. 处理 User APC(发送方死亡)
    │ if (WaitStatus == STATUS_USER_APC)
    │ {
    │ Message->flags |= SMF_SENDERDIED
    │ co_IntDeliverUserAPC()
    │ }

    ├── 13. 唤醒自己(让消息循环继续)
    │ KeSetEvent(pti->pEventQueueServer, IO_NO_INCREMENT, FALSE)

    ├── 14. 提取结果
    │ Result = Message->lResult

    └── 15. 释放消息
    if (Message->flags & SMF_RECEIVERFREE)
    FreeUserMessage(Message)

    7.5.7.3 Block vs NonBlock 同步等待对比

    维度Block 模式NonBlock 模式
    调用者 co_IntSendMessageTimeout(SMTO_BLOCK) co_IntSendMessageWithCallBack(SendMessageCallback)
    等待对象 Event + 接收方 Thread Event + 自己的消息队列 + 接收方 Thread
    消息循环 不处理 处理自己的 sent messages(避免嵌套死锁)
    用途 简单同步 嵌套发送、回调处理

    7.5.7.4 关键状态保存

    SaveMsg = pti->pusmSent; // 保存当前 pusmSent
    pti->pusmSent = Message; // 设置新的 pusmSent

    // … 等待、处理 …

    pti->pusmSent = SaveMsg; // 恢复原 pusmSent

    pusmSent 是当前正在等待回复的 sent message。ReplyMessage 通过 pusmCurrent 设置 lResult。

    7.5.7.5 cEnterCount 与栈交换

    if (pti->cEnterCount == 0)
    {
    SwapStateEnabled = KeSetKernelStackSwapEnable(FALSE);
    }
    pti->cEnterCount++;

    • cEnterCount 记录线程进入 win32k 的嵌套深度;
    • 首次进入时禁用栈交换(KeSetKernelStackSwapEnable(FALSE))以保证等待期间的栈稳定;
    • 退出时恢复原设置(可能是启用)。

    7.5.8 接收者死亡与发送者死亡

    7.5.8.1 接收者死亡(SMF_RECEIVERDIED)

    [win32ss/user/ntuser/msgqueue.c:1216-1220](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1216-L1220):

    // Receiving thread passed on and left us hanging with issues still pending.
    else if (WaitStatus == STATUS_WAIT_1)
    {
    ERR("Bk Receiving Thread woken up dead!\\n");
    Message->flags |= SMF_RECEIVERDIED;
    }

    触发条件:KeWaitForMultipleObjects 返回 STATUS_WAIT_1(即 ptirec->pEThread 对象被 signal,意为线程死亡)。

    处理:

    • 设置 SMF_RECEIVERDIED 标志;
    • 不释放消息内存(因为接收方可能还在处理中);
    • 由后续消息队列清理机制释放。

    7.5.8.2 发送者死亡(SMF_SENDERDIED)

    [win32ss/user/ntuser/msgqueue.c:1291-1301](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1291-L1301):

    // Handle User APC
    if (WaitStatus == STATUS_USER_APC)
    {
    TRACE("User APC\\n");
    // The Message will be on the Trouble list until Thread cleanup.
    Message->flags |= SMF_SENDERDIED;
    co_IntDeliverUserAPC();
    ERR("User APC Returned\\n"); // Should not see this message.
    }

    触发条件:等待期间,发送方线程收到 User APC(如 ExitThread、异步过程调用)。

    处理:

    • 设置 SMF_SENDERDIED 标志;
    • 调用 co_IntDeliverUserAPC() 投递 APC;
    • 消息将留在 usmList 麻烦列表,由接收方或线程清理机制处理。

    7.5.8.3 消息释放策略

    // Determine whether this message is being processed or not.
    if ((Message->flags & (SMF_RECEIVERBUSY|SMF_RECEIVEDMESSAGE)) != SMF_RECEIVEDMESSAGE)
    {
    Message->flags |= SMF_RECEIVERFREE;
    }

    if (!(Message->flags & SMF_RECEIVERFREE))
    {
    TRACE("Sender Freeing Message %p ptirec %p bit %d list empty %d\\n",...);
    FreeUserMessage(Message);
    }

    释放规则:

    • 如果接收方已经开始处理(SMF_RECEIVEDMESSAGE 已设置),接收方负责释放(清除 SMF_RECEIVERFREE);
    • 如果接收方未处理(如挂起时死亡),发送方负责释放。

    7.5.8.4 死亡消息清理

    当线程清理(PspExitThread)时,会调用 IntUserThreadCleanup → 清理 SentMessagesListHead 中所有未处理的消息,避免内存泄漏。


    7.5.9 co_MsqSendMessageAsync 异步发送

    7.5.9.1 函数签名

    [win32ss/user/ntuser/msgqueue.c:1013-1053](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L1013-L1053):

    BOOL FASTCALL
    co_MsqSendMessageAsync( PTHREADINFO ptiReceiver,
    HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam,
    SENDASYNCPROC CompletionCallback,
    ULONG_PTR CompletionCallbackContext,
    BOOL HasPackedLParam,
    INT HookMessage);

    7.5.9.2 实现

    BOOL FASTCALL
    co_MsqSendMessageAsync(PTHREADINFO ptiReceiver, HWND hwnd, UINT Msg,
    WPARAM wParam, LPARAM lParam,
    SENDASYNCPROC CompletionCallback,
    ULONG_PTR CompletionCallbackContext,
    BOOL HasPackedLParam, INT HookMessage)
    {
    PTHREADINFO ptiSender;
    PUSER_SENT_MESSAGE Message;

    if(!(Message = AllocateUserMessage(FALSE))) // FALSE = 不分配 pkCompletionEvent
    {
    ERR("MsqSendMessageAsync(): Not enough memory to allocate a message\\n");
    return FALSE;
    }

    ptiSender = PsGetCurrentThreadWin32Thread();

    Message->Msg.hwnd = hwnd;
    Message->Msg.message = Msg;
    Message->Msg.wParam = wParam;
    Message->Msg.lParam = lParam;
    Message->pkCompletionEvent = NULL; // 不需要 event
    Message->ptiReceiver = ptiReceiver;
    Message->ptiCallBackSender = ptiSender;
    Message->CompletionCallback = CompletionCallback;
    Message->CompletionCallbackContext = CompletionCallbackContext;
    Message->HookMessage = HookMessage;
    Message->HasPackedLParam = HasPackedLParam;
    Message->QS_Flags = QS_SENDMESSAGE;
    Message->flags = SMF_RECEIVERFREE;

    InsertTailList(&ptiReceiver->SentMessagesListHead, &Message->ListEntry);
    MsqWakeQueue(ptiReceiver, QS_SENDMESSAGE, TRUE);

    return TRUE;
    }

    7.5.9.3 与 co_MsqSendMessage 的区别

    维度co_MsqSendMessageco_MsqSendMessageAsync
    同步等待 Yes(KeWaitForMultipleObjects) No(立即返回)
    pkCompletionEvent Yes No
    ptiSender Yes No(仅 ptiCallBackSender)
    用途 同步发送 异步回调发送、线程死亡时的"火与忘"

    7.5.9.4 异步发送的回调处理

    [win32ss/user/ntuser/msgqueue.c:923-947](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L923-L947) 在 co_MsqDispatchOneSentMessage 中:

    else if ((Message->CompletionCallback) &&
    (Message->ptiCallBackSender == pti))
    {
    if (Message->QS_Flags & QS_SMRESULT)
    {
    co_IntCallSentMessageCallback(Message->CompletionCallback,
    Message->Msg.hwnd,
    Message->Msg.message,
    Message->CompletionCallbackContext,
    Message->lResult);
    Message->CompletionCallback = NULL;
    }
    else
    {
    // 消息未处理,重新排队
    RemoveEntryList(&Message->ListEntry);
    InsertTailList(&Message->ptiCallBackSender->SentMessagesListHead, &Message->ListEntry);
    MsqWakeQueue(Message->ptiCallBackSender, QS_SENDMESSAGE, TRUE);
    Ret = FALSE;
    goto Exit;
    }
    }

    处理流程:

  • 接收方 WndProc 处理消息;
  • 接收方调用 ReplyMessage 设置 QS_SMRESULT;
  • 消息被重新插入到回调发送方的 SentMessagesListHead;
  • 发送方线程的 co_MsqDispatchOneSentMessage 检测到 QS_SMRESULT,调用回调函数。

  • 7.5.10 广播、Notify 与 SendMessageCallback

    7.5.10.1 广播(HWND_BROADCAST / HWND_TOPMOST)

    [win32ss/user/ntuser/message.c:1719-1759](file:///d:/reactos/win32ss/user/ntuser/message.c#L1719-L1759) co_IntSendMessageTimeout:

    LRESULT FASTCALL
    co_IntSendMessageTimeout( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam,
    UINT uFlags, UINT uTimeout, ULONG_PTR *uResult )
    {
    if (hWnd != HWND_BROADCAST && hWnd != HWND_TOPMOST)
    return co_IntSendMessageTimeoutSingle(...);

    if (!is_message_broadcastable(Msg)) return TRUE;

    DesktopWindow = UserGetDesktopWindow();

    if (hWnd != HWND_TOPMOST)
    {
    // 桌面窗口自身也接收
    co_IntSendMessageTimeoutSingle(UserHMGetHandle(DesktopWindow), Msg, wParam, lParam, uFlags, uTimeout, uResult);
    }

    // 遍历所有顶层窗口
    Children = IntWinListChildren(DesktopWindow);
    for (Child = Children; *Child; Child++)
    {
    PWND pwnd = UserGetWindowObject(*Child);
    if (!pwnd) continue;

    // 排除菜单和切换窗口
    if (pwnd->fnid == FNID_MENU || pwnd->pcls->atomClassName == gpsi->atomSysClass[ICLS_SWITCH])
    continue;

    co_IntSendMessageTimeoutSingle(*Child, Msg, wParam, lParam, uFlags, uTimeout, uResult);
    }
    ExFreePoolWithTag(Children, USERTAG_WINDOWLIST);
    return TRUE;
    }

    is_message_broadcastable:检查消息是否允许广播。系统保留消息(如 WM_CREATE)不允许广播。

    7.5.10.2 HWND_TOPMOST 与 HWND_BROADCAST 的区别

    句柄含义接收方
    HWND_BROADCAST (-1) 所有顶层窗口 桌面 + 所有顶层
    HWND_TOPMOST (0) 所有顶层窗口 仅所有顶层(不含桌面)

    7.5.10.3 UserSendNotifyMessage 通知消息

    [win32ss/user/ntuser/message.c:2089-2135](file:///d:/reactos/win32ss/user/ntuser/message.c#L2089-L2135):

    BOOL FASTCALL
    UserSendNotifyMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
    {
    BOOL Ret = TRUE;

    if (is_pointer_message(Msg, wParam))
    {
    EngSetLastError(ERROR_MESSAGE_SYNC_ONLY);
    return FALSE;
    }

    if (hWnd == HWND_BROADCAST)
    {
    HWND *List;
    PWND DesktopWindow;

    DesktopWindow = UserGetDesktopWindow();
    List = IntWinListChildren(DesktopWindow);
    if (List != NULL)
    {
    UserSendNotifyMessage(UserHMGetHandle(DesktopWindow), Msg, wParam, lParam);
    for (i = 0; List[i]; i++)
    {
    PWND pwnd = UserGetWindowObject(List[i]);
    if (!pwnd) continue;
    if (pwnd->fnid == FNID_MENU || pwnd->pcls->atomClassName == gpsi->atomSysClass[ICLS_SWITCH])
    continue;
    Ret = UserSendNotifyMessage(List[i], Msg, wParam, lParam);
    }
    ExFreePoolWithTag(List, USERTAG_WINDOWLIST);
    }
    }
    else
    {
    Ret = co_IntSendMessageNoWait( hWnd, Msg, wParam, lParam);
    }
    return Ret;
    }

    SendNotifyMessage 特点:

    • 不等待接收方处理;
    • 指针参数(wParam、lParam)被复制到接收方进程(适合带数据的通知);
    • 不能发送指针消息(WM_POINTER*),返回 ERROR_MESSAGE_SYNC_ONLY。

    7.5.10.4 SendMessageCallback 回调发送

    [win32ss/user/ntuser/message.c:1784-1955](file:///d:/reactos/win32ss/user/ntuser/message.c#L1784-L1955) co_IntSendMessageWithCallBack:

    LRESULT FASTCALL
    co_IntSendMessageWithCallBack( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam,
    SENDASYNCPROC CompletionCallback,
    ULONG_PTR CompletionCallbackContext,
    ULONG_PTR *uResult)
    {
    // 1. 引用窗口对象
    // 2. 同线程 fast path(如果有)
    // 3. 分配 USER_SENT_MESSAGE
    // 4. 设置 CompletionCallback
    // 5. 插入接收方队列
    // 6. 立即返回
    // 7. 接收方处理后,调用回调函数
    // co_IntCallSentMessageCallback(CompletionCallback, hWnd, Msg,
    // CompletionCallbackContext, Result);
    }

    SendMessageCallback 特点:

    • 不阻塞发送方;
    • 通过 CompletionCallback 通知发送方结果;
    • 适合"需要结果但不阻塞"的场景(如 UI 线程调用后台线程)。

    7.5.10.5 4 种语义的时序图对比

    SendMessage: SendNotifyMessage: SendMessageCallback:
    A B Win32k A B Win32k A B Win32k
    ├─Send──────────────────────►├─Send────────────────────►├─Send─────────────────►
    │ │ │ │ │ │
    │ │ ├─插入队列 │ ├─插入队列 + 回调 │
    │ │ │ │ │ │
    │ ┌─GetMessage──►│ │ ├─GetMessage─────────►│ │ ├─GetMessage──────►│
    │ ├─WndProc───────►│ │ ├─WndProc─────────────►│ │ ├─WndProc─────────►│
    │ ├─ReplyMessage──►│ │ │ │ │ ├─ReplyMessage───►│
    │ └───────────────│ │ │ │ │ │ │
    │◄─Result─────────────────┤ │ │ │ │ │ │
    │ │ │ │ │ │ │ │
    │ │ ◄┤ │ │ │ │ │
    │◄─立即返回─────────────────┤ │ │ │ │ │
    │ │ │ │ ◄┤─Callback───────────┤


    7.5.11 co_MsqReplyMessage 同步响应

    7.5.11.1 函数签名

    [win32ss/user/ntuser/msgqueue.c:2483-2504](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c#L2483-L2504):

    BOOL FASTCALL
    co_MsqReplyMessage( LRESULT lResult );

    7.5.11.2 实现

    BOOL FASTCALL
    co_MsqReplyMessage( LRESULT lResult )
    {
    PUSER_SENT_MESSAGE Message;
    PTHREADINFO pti;

    pti = PsGetCurrentThreadWin32Thread();
    Message = pti->pusmCurrent;

    if (!Message) return FALSE;

    if (Message->QS_Flags & QS_SMRESULT) return FALSE;

    // SendMessageXxx || Callback msg and not a notify msg
    if (Message->ptiSender || Message->CompletionCallback)
    {
    Message->lResult = lResult;
    Message->QS_Flags |= QS_SMRESULT;
    // See co_MsqDispatchOneSentMessage, change bits already accounted for and
    // cleared and this msg is going away..
    }
    return TRUE;
    }

    7.5.11.3 工作流程

    1. 接收方 GetMessage 取到 sent message
    └─► pti->pusmCurrent = Message
    2. WndProc 处理消息
    3. 接收方调用 ReplyMessage(result)
    └─► co_MsqReplyMessage(result)
    ├─► Message->lResult = result
    └─► Message->QS_Flags |= QS_SMRESULT
    4. co_MsqDispatchOneSentMessage 检测 QS_SMRESULT
    ├─► 若是 SendMessage:KeSetEvent(pkCompletionEvent)
    └─► 若是 SendMessageCallback:调用 CompletionCallback
    5. 发送方从 KeWaitForMultipleObjects 唤醒
    └─► 提取 Message->lResult

    7.5.11.4 pusmCurrent 与 pusmSent

    字段含义设置者
    pusmCurrent 当前接收方正在处理的消息 co_MsqDispatchOneSentMessage
    pusmSent 当前发送方正在等待回复的消息 co_MsqSendMessage

    ReplyMessage 只设置 pusmCurrent.lResult——因为只有当前正在被 WndProc 处理的消息才能被 ReplyMessage 响应。

    7.5.11.5 ReplyMessage 失败情况

    情况返回原因
    pusmCurrent == NULL FALSE 当前没有 sent message 在处理
    QS_Flags & QS_SMRESULT FALSE 已调用过 ReplyMessage(不能重复)
    没有 ptiSender 也没有 CompletionCallback TRUE(但不设 QS_SMRESULT) 同步发送但无需响应(如内部消息)

    7.5.12 设计哲学问答

    Q1:为什么 SendMessage 同步等待时使用 pkCompletionEvent 而不是直接的 state variable?

    A:效率 + 线程阻塞。

    KeWaitForMultipleObjects 等待内核事件比 while (!done) Sleep(1) 忙等待效率高得多:

    • 无忙等待:发送方线程在内核态挂起,不消耗 CPU;
    • 即时唤醒:KeSetEvent 直接将线程放回就绪队列,延迟 < 1 µs;
    • 支持超时:KeWaitForMultipleObjects 接受 Timeout 参数,硬件定时器到期后自动唤醒;
    • 支持 APC:等待期间可以投递 User APC(如 ExitThread),线程能优雅退出。

    如果使用 volatile int done + 忙等待,会消耗 100% CPU,且无法优雅处理 APC 投递。

    Q2:为什么内部消息(Msg & 0x80000000)不同步等待?

    A:内部消息是 win32k 系统内部使用,不需要跨线程。

    if (Msg & 0x80000000)
    {
    TRACE("SMTS: Internal Message!\\n");
    Result = (ULONG_PTR)handle_internal_message( Window, Msg, wParam, lParam );
    // …
    }

    内部消息(WM_USER 以上,且 & 0x80000000)是 win32k 与 user32 之间的私有消息:

    • 接收方必定是当前线程(因为是 win32k 内部使用);
    • 无需线程同步,fast path 直接处理;
    • 避免无意义的线程切换开销。

    Q3:跨进程消息为什么要拒绝 WM_CREATE / WM_NOTIFY / 密码消息?

    A:防止跨进程内存访问冲突。

    • WM_CREATE:CREATESTRUCT 包含窗口创建参数(位置、样式、父窗口句柄),这些参数只在创建进程内有意义;
    • WM_NOTIFY:NMHDR 指针指向发送进程的内存,接收进程无法访问;
    • WM_GETTEXT / EM_GETLINE / EM_SETPASSWORDCHAR:在密码编辑框中,这些消息会泄露密码内容。

    Windows 的设计原则:“跨进程消息的 wParam/lParam 必须是值类型,不能是指针”。如果应用程序需要传递数据,应该使用共享内存、文件映射、命名管道、共享内存等安全的 IPC 机制。

    Q4:为什么 ReplyMessage 只能响应一次?

    A:一个消息只能有一个结果。

    if (Message->QS_Flags & QS_SMRESULT) return FALSE;

    • 第一次 ReplyMessage 设置 QS_SMRESULT;
    • 第二次调用时检测到该标志,直接返回 FALSE;
    • 强制语义一致性:消息要么未响应,要么已响应,不存在"多个结果"。

    这避免了以下场景:

    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    if (msg == WM_USER)
    {
    ReplyMessage(100); // 第一次
    // … 一些处理 …
    ReplyMessage(200); // 第二次会被忽略
    }
    return 0;
    }

    Q5:为什么 NonBlock 模式要在等待时处理自己的 sent messages?

    A:避免嵌套死锁。

    考虑以下场景:

    线程 A: SendMessageCallback(B, WM_USER)
    └─► 等待结果(NonBlock)
    └─► 期间线程 A 的消息队列中有 sent message from C
    └─► 如果不处理,线程 C 也会等 A
    └─► A 等 B,B 等 C,C 等 A ──► 死锁!

    NonBlock 模式在 KeWaitForMultipleObjects 等待期间主动处理自己的 sent messages(co_MsqDispatchOneSentMessage),打破嵌套死锁链。

    线程 A: SendMessageCallback(B, WM_USER)
    ├─► 等待 Event / 自己消息队列 / B 线程
    │ ├─► Event 触发 ──► 立即返回
    │ ├─► 自己消息队列 ──► 处理 from C ──► C 不再等 A
    │ └─► B 线程死亡 ──► SMF_RECEIVERDIED
    └─► 恢复


    总结

    视窗报文发送机制是 Windows GUI 应用程序间通信的核心,比投递消息(Post)复杂得多。本节介绍了:

  • 4 种发送语义:SendMessage(同步)、SendNotifyMessage(异步复制)、SendMessageCallback(异步回调)、PostMessage(异步无返回值);
  • USER_SENT_MESSAGE 结构:包含消息内容、收发双方线程、事件、回调、状态标志;
  • SMF_ 状态*:RECEIVERFREE、RECEIVERBUSY、RECEIVERDIED、SENDERDIED、RECEIVEDMESSAGE;
  • 同线程 fast path:直接调用 WndProc,无线程切换;
  • 跨线程同步等待:Block/NonBlock 双模式、KeWaitForMultipleObjects 等待事件;
  • 跨进程边界检查:拒绝 WM_CREATE、WM_NOTIFY、密码消息;
  • 死锁检测:MsqIsHung、SMTO_ABORTIFHUNG、SMTO_NOTIMEOUTIFNOTHUNG;
  • 接收者/发送者死亡:SMF_RECEIVERDIED、SMF_SENDERDIED、APC 投递;
  • 异步发送:co_MsqSendMessageAsync(无 event、仅回调);
  • 广播:HWND_BROADCAST / HWND_TOPMOST 遍历所有顶层窗口;
  • ReplyMessage:通过 pusmCurrent + QS_SMRESULT 同步响应。
  • 核心要点回顾:

  • 同步发送是"线程间 RPC",使用 pkCompletionEvent 同步;
  • 同线程发送走 fast path,性能高且无死锁;
  • 跨进程消息有严格的安全检查(密码、WM_CREATE、WM_NOTIFY);
  • SMF_* 状态机管理消息的完整生命周期;
  • NonBlock 模式在等待期间处理自己的 sent messages,避免嵌套死锁;
  • ReplyMessage 只能响应一次,强制结果唯一性。

  • 本章代码索引

    文件内容
    [win32ss/user/ntuser/message.c](file:///d:/reactos/win32ss/user/ntuser/message.c) co_IntSendMessage、co_IntSendMessageTimeoutSingle、co_IntSendMessageTimeout、co_IntSendMessageNoWait、co_IntSendMessageWithCallBack、UserSendNotifyMessage、co_IntDoSendMessage、PackParam、UnpackParam
    [win32ss/user/ntuser/msgqueue.c](file:///d:/reactos/win32ss/user/ntuser/msgqueue.c) co_MsqSendMessage、co_MsqSendMessageAsync、co_MsqDispatchOneSentMessage、co_MsqReplyMessage、MsqIsHung、AllocateUserMessage、FreeUserMessage
    [win32ss/user/ntuser/msgqueue.h](file:///d:/reactos/win32ss/user/ntuser/msgqueue.h) USER_SENT_MESSAGE 结构、SMF_* 标志、QS_SENDMESSAGE、QS_SMRESULT、MSQ_HUNG
    [win32ss/user/ntuser/ntstubs.c](file:///d:/reactos/win32ss/user/ntuser/ntstubs.c) NtUserSendMessage、NtUserSendMessageTimeout、NtUserSendNotifyMessage 等系统调用 stub
    [win32ss/user/ntuser/main.c](file:///d:/reactos/win32ss/user/ntuser/main.c) win32k 初始化
    [win32ss/user/ntuser/dde.c](file:///d:/reactos/win32ss/user/ntuser/dde.c) IntDdeSendMessageHook(DDE 消息发送检查)
    [win32ss/user/user32/windows/message.c](file:///d:/reactos/win32ss/user/user32/windows/message.c) user32 用户态 SendMessage 入口(与 win32k 对接)
    赞(0)
    未经允许不得转载:171主机测评 » Reactos 第 7 章 视窗报文 — 7.5 视窗报文的发送
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址