windows消息和消息隊列
與基于MS - DOS的應用程式不同,Windows的應用程式是事件(消息)驅動的。它們不會顯式地調用函數(如C運作時庫調用)來擷取輸入,而是等待windows向它們傳遞輸入。 windows系統把應用程式的輸入事件傳遞給各個視窗,每個視窗有一個函數,稱為視窗消息處理函數。視窗消息處理函數處理各種使用者輸入,處理完成後再将控制權交還給系統。視窗消息處理函數一般是在注冊一個視窗的時候指定的。你可以從典型的SDK程式中視窗消息處理函數是怎麼聲明和實作的。
對于Windows XP系統:如果頂層視窗停止響應消息超過幾秒鐘,系統會認為視窗無回應。在這種情況下,系統将隐藏這個視窗,然後生成一個影子(ghost)視窗覆寫在它上面。這個影子視窗具有着相同的Z軸順序,位置,大小,顯示屬性。影子視窗允許使用者将其移動,調整大小,甚至關閉(關閉的是停止響應的window)。此時隻有這幾個動作是被允許的,在調試模式下,系統不會生成影子視窗。
本節讨論以下主題:
Windows消息
1. 消息類型
2. 消息傳遞
3. 消息處理
4. 消息過濾
5. post message和send message
6. 消息死鎖
7. 廣播消息
8. 查詢消息
1. Windows消息
windows通過消息的形式向視窗傳遞使用者輸入。消息可以由系統和應用程式生成。該系統會為每個輸入事件産生相應的消息,
例如,使用者點選滑鼠,移動滑鼠或滾動條,或是應用程式改變了系統的某些屬性,比如說系統更改了字型資源,改變了某個視窗的
大小。 不僅如此,應用程式可以生成消息,通告發送消息指定它的窗體去執行某些任務或者是與其他的應用程式互動。
windows系統将消息發送到一個視窗消息處理函數時傳遞四個參數:視窗句柄,消息辨別符,兩個DWORD值(消息參數)。
視窗句柄辨別了該消息的目的視窗。windows使用它來确定是哪個視窗的的視窗消息處理函數收到該消息。
一個消息辨別符是一個有名字的常量,用來表明消息的意義。當一個視窗處理函數收到一條消息,它根據判斷消息辨別符來決定如何處理該消息,例如,消息辨別符WM_PAINT消息告訴視窗程式視窗的客戶區已發生變化,必須重繪。 消息參數(DWORD值)指定傳遞的資料或是資料的位址。消息參數可以是一個整型值,一個指針值。也可以為NULL。
一個視窗過程必須根據消息辨別符來确定如何解釋消息參數。
2. windows 消息類型
本節描述消息的兩種類型:
(1) 系統定義的消息
(2) 應用程式定義的消息
系統定義的消息
作業系統向應用程式發送消息來和應用程式通訊。作業系統通過消息控制應用程式的運作,向應用程式傳遞使用者輸入以及一些其他有用的資訊。
應用程式也可以發送系統定義的消息,應用程式通過這些消息去控制使用注冊視窗類建立的控件的視窗的運作。
每個系統定義的消息都有一個唯一的消息辨別符和相應的符号常量(在windows SDK的頭檔案裡定義)。符号常量通常會表明系統定義的消息所屬的類别。不同的字首表明不同的類别。一下是常見的分類:
Prefix Message category
WM General window(一般的視窗)
ABM Application desktop toolbar (應用程式桌面工具條)
BM Button control (按鈕控件)
CB Combo box control (組合框控件)
CBEM Extended combo box control(擴充的組合框控件)
CDM Common dialog box (普通的對話框)
DBT Device (裝置)
DL Drag list box (下拉清單)
DM Default push button control (預設按鈕控件)
DTM Date and time picker control(日期和時間選擇控件)
EM Edit control (編輯控件)
HDM Header control (表頭控件)
HKM Hot key control (熱鍵控件)
IPM IP address control (IP位址控件)
LB List box control (清單框控件)
LVM List view control (清單視圖控件)
MCM Month calendar control (數學月曆控件)
PBM Progress bar (進度條控件)
PGM Pager control ()
PSM Property sheet (屬性頁)
RB Rebar control (分隔條控件)
SB Status bar window (狀态條控件)
SBM Scroll bar control (滾動條控件)
STM Static control (靜态控件)
TB Toolbar (工具條)
TBM Trackbar (跟蹤欄)
TCM Tab control (頁籤控件)
TTM Tooltip control ()
TVM Tree-view control ()
UDM Up-down control ()
(2)應用程式定義的消息
應用程式可以通過建立自定義的消息,用來和自己的視窗和其他程序通訊。如果應用程式建立了自己的消息,視窗處理函數可以解析這些資訊,并作出相應的處理。
消息辨別符值的取值範圍:
該系統保留了一個消息範圍,從0x0000到0x03FF(0x03FF等于WM_USER -1)範圍. 這個範圍内的值為系統定義的消息。應用程式不能使用這些值作為自己的自定義消息。
從0x0400(數值WM_USER)到0x7FFF的值是為應用程式保留的。應用程式可以使用這個範圍内的值來定義自己的消息。
如果你的操作系用的版本(windows version)主版本為4.0版,你還可以使用0x8000(WM_APP)到0xBFF之間的值來定義自己的消息。
除此之外,應用程式還可以調用RegisterWindowMessage函數注冊一個消息時,作業系統會傳回一個介于0xC000和0xFFFF之間的一個消息辨別符。并且保證這個傳回值是系統唯一的。是以,可以避免和其他應用程式使用的消息相沖突。
3. 消息派發
windows使用兩種方法将消派發到一個視窗消息處理函數:一是将消息放到消息隊列(先進先出隊列),二是不放到消息隊列,直接發送到視窗消息處理函數,讓視窗處理函數來處理消息。
派發到消息隊列的消息被稱為排隊消息(Queued messages)。它們主要是使用者輸入事件,比如說滑鼠或鍵盤消息盤,有WM_MOUSEMOVE消息,WM_LBUTTONDOWN,WM_KEYDOWN,和WM_CHAR消息。還有一些其他的,包括WM_TIMER,WM_PAINT,以及WM_QUIT。大多數其他的消息息,這是直接發送到視窗過程,被稱為非隊列消息(non queued messages)。
(1) 隊列(Queued)消息
windows可同時顯示任意數量的視窗。此時,系統使用消息隊列來将鍵盤和滑鼠事件正确的派發到正确的視窗。
windows維護着一個系統消息隊列,以及分别為每個GUI線程維護一個各自的線程消息隊列。為了避免非GUI線程的建立線程消息隊列的開銷,所有線程建立初始化時,均不建立消息隊列。隻有當線程第一次調用GDI函數時,系統才會為線程建立消息隊列。是以那些非GUI線程是沒有消息隊列的。
每當使用者移動滑鼠,點選按鈕或鍵盤時,滑鼠或鍵盤的裝置驅動程式會将輸入轉換成消息,并将消息放在系統消息隊列裡。删windows會檢查自己的消息隊列,如果消息隊列不為空,則每次取出并删除一個消息,然後确定消息的目标視窗,然後把消息放到建立這個視窗的線程的線程消息隊列裡。線程的消息隊列接收由線程建立的視窗的所有的滑鼠和鍵盤消息。然後線程會從隊列中删除資訊,并告訴系統把它們派發到對應的視窗消息處理函數。
除了WM_PAINT, WM_TIMER和WM_QUIT消息以外,系統總是派發放在在消息隊列的末尾的消息。這将保證讓一個視窗以first-in, first-out的順序接收消息。WM_PAINT,WM_TIMER,和WM_QUIT消息,會一直被儲存在隊列中,隻有在隊列中沒有其他消息時才會被派發到視窗消息處理函數。此外,同一個視窗的多個WM_PAINT消息被合并成一個WM_PAINT消息,客戶區的所有無效部分也會被合并。這樣是為了減少視窗重繪客戶區的次數。
windows向線程消息隊列傳遞消息時,首先會填充一個MSG結構,然後将這個MSG結構複制到消息隊列。MSG中的資訊包括:目标視窗,消息辨別符,兩個消息參數,消息派發時的時間,滑鼠光标位置。一個線程可以使用PostMessage或PostThreadMessage功能向自己的消息隊列或者是其他線程的消息隊列發送消息。
應用程式可以使用GetMessage函數從自己的消息隊列中删除消息。檢視而不删除消息,用的是PeekMessage函數。
PeekMessage函數會傳回一個帶有消息資訊的MSG結構。
從消息隊列中删除消息後,應用程式可以使用DispatchMessage函數訓示系統将消息發送到一個視窗消息處理函數。 DispatchMessage的參數是是前一次調用GetMessage或PeekMessage獲得的MSG結構的指針。 DispatchMessage會傳遞視窗句柄,消息辨別符,這兩個消息參數這些資訊給視窗消息處理函數,它不會傳遞消息派發時間以及滑鼠光标位置。應用程式可以在處理消息時調用的GetMessageTime和GetMessagePos來獲得這些資訊。
線程可以使用WaitMessage函數,交出自己的控制權,當它的消息隊列中沒有消息時,調用WaitMessage函數會挂起線程,直到自己的消息隊列裡有消息時才傳回。
您可以調用SetMessageExtraInfo函數來關聯一個值給目前線程的消息隊列。然後調用GetMessageExtraInfo函數來擷取由GetMessage或PeekMessage函數得到的最後一條消息相關聯的值。你可以去msdn上看更多的關于這幾個函數的資訊。
(2) 非隊列(Nonqueued)消息
Nonqueued消息被立即送往目的地的視窗消息處理函數,繞過了系統的消息隊列和線程消息隊列。系統通常會發送nonqueued消息,來通知那些會影響視窗的事件。例如,當使用者激活一個新的應用程式視窗時,系統會發送一些列消息到視窗,包括WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSOR。這些消息通知視窗被激活,鍵盤輸入被定向到視窗,并且滑鼠光标也移到視窗的邊界内。
Nonqueued消息也有可能來源于應用程式調用系統函數。例如,系統調用SetWindowPos函數移動一個視窗後會發送WM_WINDOWPOSCHANGED消息。 一些函數也發送nonqueued消息, 有BroadcastSystemMessage,BroadcastSystemMessageEx,SendMessage,SendMessageTimeout,和SendNotifyMessage。 關于這些函數的詳細資訊,你可以查閱MSDN。
消息處理
應用程式必須删除并處理發送到它的線程消息隊列的消息。單線程應用程式通常在它的WinMain函數的消息循環,删除和分發消息到适當的視窗進行處理。多線程應用程式可以在每一個線程建立一個視窗的消息循環。以下章節描述了一個消息
循環如何工作,并講述視窗消息處理函數的作用:
(1)消息循環
(2)視窗處理函數
消息循環
一個簡單的消息循環包含調用以下三個函數:GetMessage,TranslateMessage,和DispatchMessage。請注意,如果有一個錯誤,GetMessage傳回-1 -是以,需要測試它的傳回值,來判斷為-1的情況
代碼片段:
...
MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
GetMessage函數從隊列中擷取消息,并将消息内容複制到一個MSG結構。它傳回一個非零值,除非遇到WM_QUIT消息,此種傳回FALSE并結束消息循環。在一個單線程應用程式,結束消息循環往往是在關閉應用程式的第一步。應用程式可以調用PostQuitMessage函數來響應WM_DESTROY,結束消息循環。
如果您指定一個視窗句柄作為GetMessage的第二個參數,那麼GetMessage隻擷取在消息隊列裡和這個視窗有關的消息。 GetMessage也可以在隊列中篩選消息,隻擷取指定範圍内的消息。如需有關消息過濾的詳細資訊,請參考消息過濾。
線程的消息循環必須包括TranslateMessage,如果線程需要接受鍵盤字元的輸入。每次使用者按下一個鍵,該系統産生相應的虛拟鍵消息(WM_KEYDOWN和WM_KEYUP)。虛拟鍵消息包含一個虛拟鍵碼,辨別的是被按下的鍵,而不是它相關的字元值。要獲得此值,消息循環必須包含TranslateMessage,用來将虛拟鍵消息翻譯成字元消息(WM_CHAR)的并放到應用程式的消息隊列裡。經過若幹次循環後,WM_CHAR消息會被并派發到一個視窗。
DispatchMessage函數将消息發送到到與MSG結構中的視窗句柄關聯的視窗。如果視窗句柄是HWND_TOPMOST,DispatchMessage則将消息發送到作業系統所有的頂層視窗。如果視窗句柄是NULL,DispatchMessage不做任何事。
一個應用程式的主線程初始化後,系統就啟動應用程式的消息循環,并創造至少一個視窗。一旦啟動,消息循環持續從該線程的消息隊列中删除消息,并派發他們到相應的視窗。GetMessage函數從消息清單中擷取到WM_QUIT消息時,消息循環結束。
一個消息隊列隻需要一個消息循環,即使一個應用程式包含有多個視窗。 DispatchMessage總是排程消息到正确的視窗,這是因為每個隊列中的消息是MSG結構,它包含着消息所屬的視窗的句柄。
您可以以多種方式來修改消息循環。例如,您可以從隊列中删除消息,但是不派發他們。當發送有些不帶有目的地視窗的消息時這非常有用。您也可以使用GetMessage隻擷取指定的消息,這是有用的,如果你必須你暫時繞過正常的消息隊列FIFO順序。
應用程式使用快捷鍵時,必須能夠将鍵盤消息轉換為指令消息。是以,應用程式的消息循環必須包括TranslateAccelerator函數調用。關于快捷鍵的更多資訊,請參見鍵盤加速器。
如果一個線程使用一個無模式對話框,那麼消息循環必須包括IsDialogMessage函數,以便該對話框可以接收鍵盤輸入。
(2)視窗消息處理函數
視窗消息函數接收和處理的所有發送到視窗的消息。每個視窗類有一個視窗消息處理函數,用該類建立的每個視窗使用同一視窗消息處理函數。
該系統将消息發送到一個視窗的程式,并傳遞消息的相關資訊到視窗消息處理函數,視窗消息處理函數檢查消息辨別符,根據傳過來的參數識别并處理不同的消息,
一個視窗過程通常不會忽略一個消息。如果消息沒有被處理,必須被發送給系統預設的視窗消息處理函數,這是否通過調用DefWindowProc函數,來執行一個預設的處理,并傳回一個處理的結果。視窗程式必須然後傳回該值作為自己的消息處理的結果。大多數視窗消息處理函數隻處理一少部分消息,并将其他的傳回給系統預設的視窗消息處理函數。
因為視窗消息處理函數被所有屬于同一個視窗類的視窗共享,它可以處理幾個不同的視窗的消息。要确定具體的視窗消息,視窗消息處理函數可以檢查消息結構裡的視窗句柄。