2015-03-13 75 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