Windows訊息機制之二(續)-- windows訊息和訊息佇列
與基於MS - DOS的應用程式不同,Windows的應用程式是事件(訊息)驅動的。它們不會顯式地呼叫函式(如C執行時庫呼叫)來獲取輸入,而是等待windows向它們傳遞輸入。 windows系統把應用程式的輸入事件傳遞給各個視窗,每個視窗有一個函式,稱為視窗訊息處理函式。視窗訊息處理函式處理各種使用者輸入,處理完成後再將控制權交還給系統。視窗訊息處理函式一般是在註冊一個視窗的時候指定的。你可以從典型的SDK程式中視窗訊息處理函式是怎麼宣告和實現的。
對於Windows XP系統:如果頂層視窗停止響應訊息超過幾秒鐘,系統會認為視窗無迴應。在這種情況下,系統將隱藏這個視窗,然後生成一個影子(ghost)視窗覆蓋在它上面。這個影子視窗具有著相同的Z軸順序,位置,大小,顯示屬性。影子視窗允許使用者將其移動,調整大小,甚至關閉(關閉的是停止響應的window)。此時只有這幾個動作是被允許的,在除錯模式下,系統不會生成影子視窗。
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。
4.訊息處理
應用程式必須刪除並處理髮送到它的執行緒訊息佇列的訊息。單執行緒應用程式通常在它的WinMain函式的訊息迴圈,刪除和分發訊息到適當的視窗進行處理。多執行緒應用程式可以在每一個執行緒建立一個視窗的訊息迴圈。以下章節描述了一個訊息迴圈如何工作,並講述視窗訊息處理函式的作用:
(1)訊息迴圈
(2)視窗處理函式
(1)訊息迴圈
一個簡單的訊息迴圈包含呼叫以下三個函式: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函式,來執行一個預設的處理,並返回一個處理的結果。視窗程式必須然後返回該值作為自己的訊息處理的結果。大多數視窗訊息處理函式只處理一少部分訊息,並將其他的返回給系統預設的視窗訊息處理函式。
因為視窗訊息處理函式被所有屬於同一個視窗類的視窗共享,它可以處理幾個不同的視窗的訊息。要確定具體的視窗訊息,視窗訊息處理函式可以檢查訊息結構裡的視窗控制代碼。