2015-03-13 72 views
3

昨天我遇到了我见过的最奇怪的问题。 我写了一个应该得到USB插头通知的模块。 为此,我创建了一个虚拟窗口,并使用一些界面的GUID将其注册到设备更改通知。PeekMessage触发WndProc回调

当调用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,并按照计算器社区成员的意见,我为了纳入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 

寄存器完全一样!(无参数传递到堆栈,64位..)

在这两种情况下(奇怪的错误流量预计)我走进在NtUserPeekMessage,事实证明,在WndProc回调从才会触发内部系统调用!

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

我找不到MSDN上的任何文档或在互联网上解释现象。

我真的很喜欢一些帮助, 在此先感谢。

+0

WM_DEVICECHANGE总是发送,绝不会发布。 SendMessage()不会任意中断程序的UI线程,这会导致可怕的重入问题。它需要等待,直到它得到一个线程闲置的信号,并直接调用窗口过程不会引起任何麻烦。 Get/PeekMessage()是那个信号。 – 2015-03-13 13:43:10

+0

@HansPassant非常感谢你:) – CodeNinja 2015-03-13 13:58:50

回答

5

这是如预期的,并被记录在案。 PeekMessage是分派发送消息的函数之一。从documentation

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

同一文档后面

然后:

在通话过程中,该系统提供了未决,非排队的消息,那就是,发送到通过使用SendMessage,SendMessageCallback调用线程拥有窗口消息, SendMessageTimeout或SendNotifyMessage函数。

documentationSendMessage说,这(我的重点):

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

通过邮件检索代码,文档的功能类似于GetMessagePeekMessage。还有一些其他的,我手头上没有全面的清单。

+0

感谢你的快速回复。我不明白:peekmessage如何选择是否自己发送消息?通过写'在线程之间发送的消息被处理'是否意味着这个消息是从另一个线程(内核线程?)发送的,以通知USB插头? – CodeNinja 2015-03-13 13:50:09

+0

@CodeNinja它发送**发送**消息。 “PeekMessage”检索**发布的**消息。 – 2015-03-13 13:53:02

+0

谢谢大卫!它现在非常有意义。你会如何推荐我注册'Windows消息'是否发送或发布到我的MsgWait循环? – CodeNinja 2015-03-13 13:55:16