Windows 訊息陷阱

來源:互聯網
上載者:User

  也許題目有些誇張,但是Windows訊息方面確實存在一些不去探究就摸不著頭腦的事情,這種問題不是明顯錯誤,不會拋出異常,但卻是最棘手的問題,給調試帶來很大麻煩,所以我將實際遇到的問題整理如下,以供參考。

 一、Windows 訊息以及訊息處理演算法Windows以訊息驅動的方式,使得線程能夠通過處理訊息來響應外界。Windows 為每個需要接受訊息和處理訊息的線程建立訊息佇列(包括髮送訊息佇列,登記訊息佇列,輸入訊息佇列,響應訊息佇列),其中發送訊息佇列儲存其他線程通過SendMessage發送給該線程建立視窗的訊息,登記訊息佇列儲存通過PostMessage發送給該線程或者該線程建立視窗的訊息,輸入訊息佇列儲存系統的輸入(包括鍵盤,滑鼠輸入),響應訊息佇列包含該線程調用SendMessage給指定視窗的視窗函數處理完後通知該線程的資訊。 Windows通過QS_SENDMESSAGE、QS_POSTMESSAGE、QS_QUIT、QS_INPUT、QS_PAINT、QS_TIMER表示是否有發送訊息、登記訊息、退出訊息、輸入訊息、重繪訊息、定時訊息。訊息的優先順序是QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER。 Windows處理訊息的方式大概是這樣的:訊息迴圈偽演算法:BOOL bRet = FALSE;MSG msg;while ((bRet = GetMessage(&msg, NULL, 0, 0))) {         if (bRet == -1) break; // On Error exit the loop         TranslateMessage(&msg); //轉換訊息         DispatchMessage(&msg); //發送訊息,其實就是調用指定視窗的視窗函數}  GetMessage偽演算法如下:BOOL GetMessage(MSG *lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax) {         //查看QS_SENDMESSAGE標誌,如果有的話迴圈處理,直到沒有訊息位置         DWORD dwRetVal = 0;         ThreadInfo threadInfo;  FLAG_SENDPROCLOOP:         GetThreadInfo(GetCurrentThreadId(), &threadInfo);         while (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) {                   //從發送訊息佇列中擷取訊息                   dwReturnVal = GetMsgFromQueue(QUEUE_SEND, lpMsg, hWnd,wMsgFilterMin, wMsgFilterMax);                   //判斷是否取到訊息,有則調用視窗函數,無則複為QS_SENDMESSAGE標誌                   If (dwReturnVal == GETMESSAGE_HASMESSAGE) {                            //調用指定視窗的視窗函數                            CallWindowProc(hWnd, &threadInfo, lpMsg);                   }                   else {                            QS_SENDMESSAGE = QS_SIGNALRESET;                             break;                   }         }         //在繼續處理之前再次檢查發送訊息佇列         if (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) goto FLAG_SENDPROCLOOP;         //檢查發送訊息佇列, 如果有訊息則取發送訊息         //判斷是否還有發送訊息,沒有了則複位QS_POSTMESSAGE標誌         if (threadInfo.QS_POSTMESSAGE == QS_SIGNALSET) {                   dwReturnVal = GetMsgFromQueue(QUEUE_POST, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);                   if (dwReturnVal == GETMESSAGE_LASTMESSAGE)                            threadInfo.QS_POSTMESSAGE = QS_SIGNALRESET;                                      return TRUE;         }                  //如果退出標誌被置位         if (threadInfo.QS_QUIT == QS_SIGNALSET) {                   threadInfo.QS_QUIT = QS_SIGNALRESET;                   FillMessage(lpMsg, MESSAGE_QUIT);                   return FALSE;         }          //檢查輸入訊息佇列         if (threadInfo.QS_INPUT == QS_SIGNALSET) {                   DWORD dwRetVal = GetMessageFromQueue(QUEUE_INPUT, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);                   //檢查是否有鍵盤,滑鼠訊息                   if (Test(dwRetVal, QS_KEY) == QS_LASTMOUSEKEYMESSAGE)                             threadInfo.QS_KEY = QS_SIGNALRESET;                   if (Test(dwRetVal, QS_MOUSEBUTTON) == QS_LASTMOUSEMESSAGE)                             threadInfo.QS_MOUSEBUTTON = QS_SIGNALRESET;                    return TRUE;         }          //測試QS_PAINT         if (threadInfo.QS_PAINT == QS_SIGNALSET) {                   //填充MSG,如果沒有視窗過程確認視窗,則複位QS_PAINT標誌                   //...                   //返回TRUE                   threadInfo.QS_PAINT = QS_SIGNALRESET;                   return TRUE;         }          if (threadInfo.QS_TIMER == QS_SIGNALSET) {                   //填充MSG,如果沒有定時器報時,則複位QS_TIMER標誌                   //...                   //返回TRUE                   return TRUE;         }          //等待有訊息到達         dwRetVal = MsgWaitForMultipleObjectsEx(...);         if (...)                   goto FLAG_SENDPROCLOOP;          //等待失敗         return FALSE;} 上面要注意的是各種訊息被處理的優先順序順序,在發送隊列中有發送訊息時,GetMessage不返回,直到將發送隊列中訊息處理完畢為止,然後複位QS_SENDMESSAGE,沒有發送訊息時,GetMessage才查看登記訊息,如果沒有登記訊息,則依著優先順序從高到低的順序依次處理各種訊息。 如果此過程中發現了優先順序低的訊息,則GetMessage填充一個MSG,然後返回。如果是QS_QUIT被置位,則GetMessage返回FALSE,否則返回TRUE。 當GetMessage返回FALSE時,訊息迴圈也就結束了。看訊息迴圈可知,當訊息迴圈再次調用GetMessage時,依然按照優先順序順序依次處理各種訊息。請注意SendMessage發送到目標線程訊息佇列的訊息在目標線程調用GetMessage時被處理掉,直到沒有發送訊息為止GetMessage才回去查詢其他訊息,如果有訊息GetMessage取到訊息返回,否則GetMessage使得線程陷入IDLE狀態,被掛起,當有訊息到達線程時GetMessage被喚醒,擷取訊息返回。二、Windows 訊息之WM_TIMERWM_TIMER訊息的優先順序最低,所以在有其他訊息的情況下,WM_TIMER訊息得不到處理,這也是我以前使用SetTimer註冊一個回呼函數,而回呼函數一直未被調用的原因。因為我在UI環境中使用,處理WM_PAINT訊息時又觸發了介面的重繪,導致了始終有WM_PAINT訊息要處理,WM_TIMER於是得不到處理的機會。處理WM_PAINT訊息時要小心,不然程式就可能消耗很高的cpu,並且使得低於WM_PAINT優先順序的WM_TIMER得不到處理。三、Windows 訊息相關函數之SendMessageTimeOutSendMessageTimeOut是發送訊息,在訊息被處理或者逾時的情況下會返回。但是查閱了MSDN和Windows核心編程,都沒有發現這個逾時值設為0時有什麼效果。直到最近一次在服務中對外廣播訊息,將此值設為0,服務啟動後在沒有將服務狀態設為RUNNING時調用SendMessageTimeOut對外廣播訊息,逾時值設為0,原本以為該函數會立刻返回,但是調用導致了線程的掛起。由於處理廣播訊息的另外線程一直在等待RUNNING狀態,而服務又等待外界處理完該訊息然後繼續,這就產生了一個死結。 這都是逾時值設定為0引起的後果。現在看來逾時值設為0就等同於調用SendMessage了。上面沒有分析線程訊息(即通過PostThreadMessage發送的訊息),關於Windows訊息更詳細的解釋,以及訊息處理機制請參考《Windows 核心編程第26章》
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.