【问题标题】:PeekMessage triggers WndProc callbackPeekMessage 触发 WndProc 回调
【发布时间】:2015-05-15 23:50:25
【问题描述】:

昨天我遇到了我见过的最奇怪的问题。 我写了一个模块,应该会收到关于 USB 插头的通知。 为此,我创建了一个虚拟窗口,并使用某些接口的GUID 将其注册到设备更改通知。

调用PeekMessage 时出现奇怪的错误。 此时,某些原因,窗口的 WndProc 回调被调用,只有当偷看的消息是WM_DEVICECHANGE(我们在上面的代码中注册)。 在任何其他消息上,DispatchMessage 都会按预期触发回调。

代码:

NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = guid;
not = RegisterDeviceNotification(
    hWnd,     // events recipient
    &NotificationFilter,        // type of device
    DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
);

为了将此模块与我的其余异步代码合并,使用Reactor 设计模式和Windows Events,并遵循stackoverflow 社区成员的建议,我合并了MsgWaitForMultipleObjects 以监听事件和 windows 消息。

代码:

for (;;)
{
    dwRetval = MsgWaitForMultipleObjects(cntEvents, arrEvents, FALSE, INFINITE, QS_ALLINPUT);
    switch (dwRetval)
    {
    case WAIT_FAILED:
        // failed. TODO: status
        break;
    // TODO: handle abandoned.
    default:
        if (dwRetval == cntEvents)
        {
            // Message has popped.
            BOOL x = PeekMessage(&tMsg, hWnd, 0, 0, PM_REMOVE); <---- WM_DEVICECHANGE triggers the callback
            if (x)
            {
                TranslateMessage(&tMsg);
                DispatchMessage(&tMsg);
            }
        }
        else if (dwRetval < cntEvents)
        {
            // event signaled
        }
        else
        {
            // TODO: status. unexpected.
            return FALSE; // unexpected failure
        }
        break;
    }
}

我反汇编了代码,并在调用NtUserPeekMessage之前比较了寄存器

成功调用时注册:

RAX = 00000059A604EFB0 RBX = 0000000000000000 RCX = 00000059A604EF18
RDX = 0000000000070C62 RSI = 00000059A604EF18 RDI = 0000000000070C62
R8  = 0000000000000000 R9  = 0000000000000000 R10 = 00007FF71A65D800
R11 = 0000000000000246 R12 = 0000000000000000 R13 = 0000000000000000
R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF954562AA1
RSP = 00000059A604EE70 RBP = 0000000000000000 EFL = 00000200 

注册未知回调触发调用:

RAX = 00000059A604EFB0 RBX = 0000000000000000 RCX = 00000059A604EF18
RDX = 0000000000070C62 RSI = 00000059A604EF18 RDI = 0000000000070C62
R8  = 0000000000000000 R9  = 0000000000000000 R10 = 00007FF71A65D800
R11 = 0000000000000246 R12 = 0000000000000000 R13 = 0000000000000000
R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF954562AA1
RSP = 00000059A604EE70 RBP = 0000000000000000 EFL = 00000200 

寄存器一模一样!(栈上不传参数,64bit..)

在这两种情况下(奇怪的错误预期的流程)我进入NtUserPeekMessage,结果WndProc回调仅由内部触发系统调用!

00007FF954562A80  mov         r10,rcx  
00007FF954562A83  mov         eax,1003h  
00007FF954562A88  syscall  

我在 MSDN 上找不到任何文档或在 Internet 上找到对该现象的解释。

我真的很想得到一些帮助, 提前致谢。

【问题讨论】:

  • WM_DEVICECHANGE 始终发送,从未发布。 SendMessage() 不会任意中断程序的 UI 线程,这会导致可怕的重入问题。它需要等到它收到线程空闲的信号并且直接调用窗口过程不会造成任何麻烦。 Get/PeekMessage() 就是那个信号。
  • @HansPassant 非常感谢你:)

标签: c winapi msdn internals


【解决方案1】:

这符合预期,并已记录在案。 PeekMessage 是发送已发送消息的函数之一。来自documentation

调度传入的已发送消息,检查线程消息队列中的已发布消息,并检索消息(如果存在)。

然后在同一个文档中:

在此调用期间,系统传递待处理的非排队消息,即使用 SendMessage、SendMessageCallback、SendMessageTimeout 或 SendNotifyMessage 函数发送到调用线程拥有的窗口的消息。

SendMessagedocumentation 这么说(我强调):

如果指定的窗口是由调用线程创建的,则窗口过程会立即作为子例程调用。如果指定的窗口是由不同的线程创建的,系统将切换到该线程并调用适当的窗口过程。线程间发送的消息只有在接收线程执行消息检索代码时才会被处理

通过消息检索代码,文档表示像 GetMessagePeekMessage 这样的函数。还有一些其他的,我手头没有完整的清单。

【讨论】:

  • 感谢您的快速回复。我不明白:peekmessage 如何选择是否自行发送消息?写Messages sent between threads are processed是否意味着这条消息是从另一个线程(内核线程?)发送来通知USB插头的?
  • @CodeNinja 它调度 sent 消息。 PeekMessage 检索已发布消息。
  • 谢谢大卫!现在很有意义。您如何建议我注册 Windows Messages,无论是发送还是发布到我的 MsgWait 循环?
  • 这取决于发件人。有些消息总是被发送,有些消息总是被发布。 WM_DEVICECHANGE 是已发送的消息。 WM_MOUSEMOVE 是已发布的消息。不同类别的消息以不同的方式传递。
  • @DavidHeffernan 所以不可能通过PeekMessage(..., PM_REMOVE)从队列中删除发送的消息吗?只有已发布的消息?
猜你喜欢
  • 2021-12-26
  • 2019-04-01
  • 2022-07-26
  • 1970-01-01
  • 2011-02-12
  • 2020-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多