Windows 線程消息隊列和GetMessage實現內幕

  注:轉自http://blog.csdn.net/FreeWave/article/details/2056469?reload算法

       清晰地講解了Windows線程的消息隊列和GetMessage內幕。好文。編程

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

1、Windows 消息以及消息處理算法oop

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處理消息的方式大概是這樣的:spa

消息循環僞算法:.net

BOOL bRet = FALSE;線程

MSG msg;調試

while ((bRet = GetMessage(&msg, NULL, 0, 0))) {blog

         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;

        

         if (threadInfo.QS_POSTMESSAGE == QS_SIGNALSET) {

                       //從登記消息隊列中獲取消息

                   dwReturnVal = GetMsgFromQueue(QUEUE_POST, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);

                      //判斷是否還有登記消息,沒有了則復位QS_POSTMESSAGE標誌

                   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被喚醒,獲取消息返回。

2、Windows 消息之WM_TIMER

WM_TIMER消息的優先級最低,因此在有其餘消息的狀況下,WM_TIMER消息得不處處理,這也是我之前使用SetTimer註冊一個回調函數,而回調函數一直未被調用的緣由。由於我在UI環境中使用,處理WM_PAINT消息時又觸發了界面的重繪,致使了始終有WM_PAINT消息要處理,WM_TIMER因而得不處處理的機會。處理WM_PAINT消息時要當心,否則程序就可能消耗很高的cpu,而且使得低於WM_PAINT優先級的WM_TIMER得不處處理。

3、Windows 消息相關函數之SendMessageTimeOut

SendMessageTimeOut是發送消息,在消息被處理或者超時的狀況下會返回。可是查閱了MSDN和Windows核心編程,都沒有發現這個超時值設爲0時有什麼效果。直到最近一次在服務中對外廣播消息,將此值設爲0,服務啓動後在沒有將服務狀態設爲RUNNING時調用SendMessageTimeOut對外廣播消息,超時值設爲0,本來覺得該函數會馬上返回,可是調用致使了線程的掛起。因爲處理廣播消息的另外線程一直在等待RUNNING狀態,而服務又等待外界處理完該消息而後繼續,這就產生了一個死鎖。 這都是超時值設置爲0引發的後果。如今看來超時值設爲0就等同於調用SendMessage了。

上面沒有分析線程消息(即經過PostThreadMessage發送的消息),關於Windows消息更詳細的解釋,以及消息處理機制請參考《Windows 核心編程第26章》

相關文章
相關標籤/搜索