注:轉自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章》