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_TIMER
WM_TIMER訊息的優先順序最低,所以在有其他訊息的情況下,WM_TIMER訊息得不到處理,這也是我以前使用SetTimer註冊一個回呼函數,而回呼函數一直未被調用的原因。因為我在UI環境中使用,處理WM_PAINT訊息時又觸發了介面的重繪,導致了始終有WM_PAINT訊息要處理,WM_TIMER於是得不到處理的機會。處理WM_PAINT訊息時要小心,不然程式就可能消耗很高的cpu,並且使得低於WM_PAINT優先順序的WM_TIMER得不到處理。
三、Windows 訊息相關函數之SendMessageTimeOut
SendMessageTimeOut是發送訊息,在訊息被處理或者逾時的情況下會返回。但是查閱了MSDN和Windows核心編程,都沒有發現這個逾時值設為0時有什麼效果。直到最近一次在服務中對外廣播訊息,將此值設為0,服務啟動後在沒有將服務狀態設為RUNNING時調用SendMessageTimeOut對外廣播訊息,逾時值設為0,原本以為該函數會立刻返回,但是調用導致了線程的掛起。由於處理廣播訊息的另外線程一直在等待RUNNING狀態,而服務又等待外界處理完該訊息然後繼續,這就產生了一個死結。 這都是逾時值設定為0引起的後果。現在看來逾時值設為0就等同於調用SendMessage了。
上面沒有分析線程訊息(即通過PostThreadMessage發送的訊息),關於Windows訊息更詳細的解釋,以及訊息處理機制請參考《Windows 核心編程第26章》

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/FreeWave/archive/2008/01/21/2056469.aspx

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.