天天看點

Windows消息機制流程 Windows消息機制要點

文章一:較為精簡的windows消息機制的解釋

【摘自】http://www.mianwww.com/html/2012/04/15213.html

Windows消息機制的流程:

1.Windows中有一個系統消息隊列,對于每一個正在執行的Windows應用程式,系統為其建立一個“消息隊列”,即應用程式隊列,用來存放該程式可能建立的各種視窗的消息。應用程式中含有一段稱作“消息循環”的代碼,用來從消息隊列中檢索這些消息并把它們分發到相應的視窗函數中。

2.Windows為目前執行的每個Windows程式維護一個「消息隊列」。在發生輸入事件之後,Windows将事件轉換為一個「消息」并将消息放入程式的消息隊列中。程式通過執行一塊稱之為「消息循環」的程式代碼從消息隊列中取出消息:

while(GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

TranslateMessage(&msg);将msg結構傳給Windows,進行一些鍵盤轉換。

DispatchMessage (&msg);又将msg結構回傳給Windows。然後,Windows将該消息發送給适當的視窗消息處理程式,讓它進行處理。

SendMessage()與PostMessage()之間的差別是什麼?

它們兩者是用于向應用程式發送消息的。PostMessage()将消息直接加入到應用程式的消息隊列中,不等程式傳回就退出;而SendMessage()則剛好相反,應用程式處理完此消息後,它才傳回。

文章二.下面這篇文章将windows消息循環和消息處理機制描述的很到位。

【轉自】http://www.cnblogs.com/railgunman/archive/2010/12/10/1902446.html

Windows消息機制要點

1. 視窗過程 

    每個視窗會有一個稱為視窗過程的回調函數(WndProc),它帶有四個參數,分别為:視窗句柄(Window Handle),消息ID(Message ID),和兩個消息參數(wParam, lParam), 當視窗收到消息時系統就會調用此視窗過程來處理消息。(是以叫回調函數)

2 消息類型 

1) 系統定義消息(System-Defined Messages) 

在SDK中事先定義好的消息,非使用者定義的,其範圍在[0x0000, 0x03ff]之間, 可以分為以下三類: 

1> 視窗消息(Windows Message) 

與視窗的内部運作有關,如建立視窗,繪制視窗,銷毀視窗等。可以是一般的視窗,也可以是Dialog,控件等。 

如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL... 

2> 指令消息(Command Message) 

與處理使用者請求有關, 如單擊菜單項或工具欄或控件時, 就會産生指令消息。 

WM_COMMAND, LOWORD(wParam)表示菜單項,工具欄按鈕或控件的ID。如果是控件, HIWORD(wParam)表示控件消息類型 

3> 控件通知(Notify Message) 

控件通知消息, 這是最靈活的消息格式, 其Message, wParam, lParam分别為:WM_NOTIFY, 控件ID,指向NMHDR的指針。NMHDR包含控件通知的内容, 可以任意擴充。 

2) 程式定義消息(Application-Defined Messages) 

使用者自定義的消息, 對于其範圍有如下規定: 

WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10) 

WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4) 

RegisterWindowMessage: 0xC000-0xFFFF

3 消息隊列(Message Queues) 

Windows中有兩種類型的消息隊列 

1) 系統消息隊列(System Message Queue) 

這是一個系統唯一的Queue,裝置驅動(mouse, keyboard)會把操作輸入轉化成消息存在系統隊列中,然後系統會把此消息放到目标視窗所在的線程的消息隊列(thread-specific message queue)中等待處理 

2) 線程消息隊列(Thread-specific Message Queue) 

每一個GUI線程都會維護這樣一個線程消息隊列。(這個隊列隻有線上程調用GDI函數時才會建立,預設不建立)。然後線程消息隊列中的消息會被送到相應的視窗過程(WndProc)處理. 

注意: 線程消息隊列中WM_PAINT,WM_TIMER隻有在Queue中沒有其他消息的時候才會被處理,WM_PAINT消息還會被合并以提高效率。其他所有消息以先進先出(FIFO)的方式被處理。

4 隊列消息(Queued Messages)和非隊列消息(Non-Queued Messages) 

1)隊列消息(Queued Messages) 

消息會先儲存在消息隊列中,消息循環會從此隊列中取消息并分發到各視窗處理 

如滑鼠,鍵盤消息。 

2) 非隊列消息(NonQueued Messages) 

消息會繞過系統消息隊列和線程消息隊列直接發送到視窗過程被處理 

如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED 

注意: postMessage發送的消息是隊列消息,它會把消息Post到消息隊列中; SendMessage發送的消息是非隊列消息, 被直接送到視窗過程處理

5 PostMessage(PostThreadMessage), SendMessage 

PostMessage:把消息放到指定視窗所在的線程消息隊列中後立即傳回。 PostThreadMessage:把消息放到指定線程的消息隊列中後立即傳回。 

SendMessage:直接把消息送到視窗過程處理, 處理完了才傳回。

6 GetMessage, PeekMessage 

PeekMessage會立即傳回    可以保留消息 

GetMessage在有消息時傳回  會删除消息

7 TranslateMessage, TranslateAccelerator 

TranslateMessage: 把一個virtual-key消息轉化成字元消息(character message),并放到目前線程的消息隊列中,消息循環下一次取出處理。 

TranslateAccelerator: 将快捷鍵對應到相應的菜單指令。它會把WM_KEYDOWN 或 WM_SYSKEYDOWN轉化成快捷鍵表中相應的WM_COMMAND 或WM_SYSCOMMAND消息, 然後把轉化後的 WM_COMMAND或WM_SYSCOMMAND直接發送到視窗過程處理, 處理完後才會傳回。

8(消息死鎖( Message Deadlocks) 

假設有線程A和B, 現在有以下下步驟 

1) 線程A SendMessage給線程B, A等待消息線上程B中處理後傳回 

2) 線程B收到了線程A發來的消息,并進行處理, 在處理過程中,B也向線程A SendMessgae,然後等待從A傳回。 

因為此時, 線程A正等待從線程B傳回, 無法處理B發來的消息, 進而導緻了線程A,B互相等待, 形成死鎖。多個線程也可以形成環形死鎖。 

可以使用 SendNotifyMessage或SendMessageTimeout來避免出現死鎖。

9 BroadcastSystemMessage 

我們一般所接觸到的消息都是發送給視窗的, 其實, 消息的接收者可以是多種多樣的,它可以是應用程式(applications), 可安裝驅動(installable drivers), 網絡裝置(network drivers), 系統級裝置驅動(system-level device drivers)等, 

BroadcastSystemMessage這個API可以對以上系統元件發送消息。

      一、引言 

随着Windows作業系統的不斷推廣,衆多軟體開發包都提供有開發基于Windows平台應用軟體的功能。雖然這些開發包不盡相同,流行的有Visual C++、Visual Basic、Delphi、C++ Builder 等多種,但由這些不同語言開發的軟體有一點卻是相同的--都是運作于Windows 操作平台,都必須接受Windows 的運作機制。作為Windows 作業系統靈魂的消息機制也就必然為衆多用不同語言開發的Windows作業系統下運作的應用程式所接受。是以,要編寫深入的Windows程式,就必須對 Windows的運作機制有很好的認識和了解。本文下面将對Windows作業系統下的消息運作機制做較為深入的剖析。 

二、Windows事件驅動機制 

我們當中不少使用VC、Delphi等作為開發語言的程式員是一步步從DOS下的Basic、C++中走過來的,而且大多在剛開始學習程式設計時也是先從 DOS下的程式設計環境入手的,是以在習慣了DOS下的過程驅動形式的順序程式設計方法後,往往在向Windows下的開發環境轉型的過程中會對 Windows所采取的事件驅動方式感到無法适應。因為DOS和Windows這兩種作業系統的運作機制是截然不同的,DOS下的任何程式都是使用順序的、過程驅動的程式設計方法。這種程式都有一個明顯的開始、明顯的過程以及一個明顯的結束,是以通過程式就能直接控制程式事件或過程的全部順序。即使是在處理異常時,處理過程也仍然是順序的、過程驅動的結構。而Windows的驅動方式則是事件驅動的,即程式的流程不是由事件的順序來控制,而是由事件的發生來控制,所有的事件是無序的,所為一個程式員,在編寫程式時,并不知道使用者會先按下哪個按紐,也就不知道程式先觸發哪個消息。是以我們的主要任務就是對正在開發的應用程式要發出的或要接收的消息進行排序和管理。事件驅動程式設計是密切圍繞消息的産生與處理而展開的,一條消息是關于發生的事件的消息。 

三、Windows的消息循環 

Windows作業系統為每一個正在運作的應用程式保持有一個消息隊列。當有事件發生後,Windows并不是将這個激發事件直接送給應用程式,而是先将其翻譯成一個Windows消息,然後再把這個消息加入到這個應用程式的消息隊列中去。應用程式需要通過消息循環來接收這些消息。在MFC中使用了對 WinAPI進行了很好封裝的類庫,雖然可以為程式設計提供一個面向對象的界面,使Windows程式員能夠以面象對象的方式進行程式設計,把那些進行SDK程式設計時最繁瑣的部分提供給程式員,使之專注于功能的實作,但是由于引入了很好的封裝特性,使我們不能直接操縱部分核心代碼。對于消息的循環和接收也隻是通過類似于下面的消息映射予以很簡單的表示: 

BEGIN_MESSAGE_MAP(CTEMMSView, CFormView) 

//{ { AFX_MSG_MAP(CTEMMSView) 

ON_WM_LBUTTONDOWN() 

ON_COMMAND(ID_OPENDATA, OnOpenData) 

ON_WM_TIMER() 

ON_WM_PAINT() 

//} } AFX_MSG_MAP 

END_MESSAGE_MAP() 

雖然上述消息映射在程式設計過程中處理消息非常簡練友善,但顯然是難于了解消息是如何參與循環和分發的。是以有必要通過SDK(Software Developers Kit,軟體開發工具箱)代碼深入到被MFC封裝的Windows程式設計的核心中來研究其具體是如何工作的。在SDK程式設計中,一般是在Windows應用程式的入口點WinMain函數中添加處理消息循環的代碼以檢索Windows送來的消息,然後WinMain再把這些消息配置設定給相應的視窗函數并處理它們: 

…… 

MSG msg; //定義消息名 

while (GetMessage (& msg, NULL, 0, 0)) 

TranslateMessage (& msg) ; //翻譯消息 

DispatchMessage (& msg) ; //撤去消息 

return msg.wParam ; 

上述幾句雖然簡單但卻是所有Windows程式的關鍵代碼,擔負着擷取、解釋和分發消息的任務,下面就重點對其功能和作用進行分析: 

MSG結構在頭檔案中定義如下: 

typedef struct tagMSG 

HWND hwnd; 

UINT message; 

WPARAM wParam; 

LPARAM lParam; 

DWORD time; 

POINT pt; 

} MSG, *PMSG; 

其資料成員的具體意義如下: 

hwnd:消息将要發送到的那個視窗的句柄,用這個參數可以決定讓哪個視窗接收消息。 

message:消息号,它唯一辨別了一種消息類型。每種消息類型都在Windows檔案進行了預定義。 

wParam:一個32位的消息參數,這個值的确切意義取決于消息本身。 

lParam:同上。 

time:消息放入消息隊列中的時間,在這個域中寫入的并非當時日期,而是從Windows啟動後所測量的時間值。Windows用 

這個域來使用消息保持正确的順序。 

pt:消息放入消息隊列時的滑鼠坐标。 

消息循環以GetMessage調用開始,它從消息隊列中取出一個消息。該函數的四個參數可以有控制地擷取消息,第一個參數指定要接收消息的MSG結構的位址,第二個參數表示視窗句柄,一般将其設定為空,表示要擷取該應用程式建立的所有視窗的消息;第三、四參數用于指定消息範圍。後面三個參數被設定為預設值,用于接收發送到屬于這個應用程式的任何一個視窗的所有消息。在接收到除WM_QUIT之外的任何一個消息後,GetMessage()傳回 TRUE;如果GetMessage收到一個WM_QUIT消息,則傳回FALSE以退出消息循環,終止程式運作。是以,在接收到WM_QUIT之前,帶有GetMessage()的消息循環可以一直循環下去。當除WM_QUIT的消息用GetMessage讀入後,首先要經過函數 TranslateMessage()對其進行解釋,但對大多數消息來說并不起什麼作用。這裡起關鍵作用的是DispatchMessage()函數,把由GetMessage擷取的Windows消息傳送給在MSG結構中為視窗所指定的視窗過程。在消息處理函數處理完消息之後,代碼又循環到開始去接收另一個消息,這樣就完成了一個完整的消息循環。 

由于Windows作業系統是一種非剝奪式多任務作業系統。隻有在應用程式主動交出CPU控制權後,Windows才能把控制權交給其他應用程式。在消息循環中,一定要有能交出控制的系統函數才能實作協同式多任務操作。能完成該功能的隻有GetMessage、PeekMessage和 WaitMessage這三個函數,如果在應用程式中長期不去調用這三個函數之一其他任務則無法執行。GetMessage函數在找不到等待應用程式處理的消息時,會自動交出控制權,由Windows把CPU的控制權交給其他等待擷取控制權的應用程式。由于任何Windows應用程式都含有一個消息循環,這種隐式交出控制權的方式可以保證合并各個應用程式共享控制權。一旦發往該應用程式的消息到達應用程式隊列,即開始執行GetMessage語句的下一條語句。使用GetMessage函數的消息循環在消息隊列中沒有消息時将等待,如果需要,可以利用這段時間進行I/O端口操作等耗時操作,不過需要在消息循環中使用PeekMessage函數來代替GetMessage。使用PeekMessage的方法同GetMessage類似,下面是一段使用 PeekMessage函數的消息循環的典型例子: 

MSG msg; 

BOOL bDone=FALSE; 

do{ 

if(PeekMessage(& msg,NULL,0,0,PM_REMOVE)){ 

if(msg.message==WM_QUIT) 

bDone=TRUE; 

else{ 

TranslateMessage(& msg); 

DispatchMessage(& msg); 

//無消息處理,進行長時間操作 

else{ 

……//長時間操作 

} while(!bDone) 

…… 

無論應用程式消息隊列中是否有消息,PeekMessage函數都立即傳回,如果希望等待新消息入隊,可以利用無傳回值的函數WaitMessage配合PeekMessage進行消息循環。 

四、對Windowds消息的處理 

視窗過程處理消息通常以switch語句開始,對于它要處理的每一條消息ID都跟有一條case語句,這在功能上同MFC的消息映射有些類似: 

switch(uMsgId) 

case WM_TIMER: 

//對WM_TIMER定時器消息的處理過程 

return 0; 

case WM_LBUTTONDOWN: 

//對WM_ LBUTTONDOWN滑鼠左鍵單擊消息的處理過程 

ruturn 0; 

…… 

default: 

//其他消息由這個預設處理函數來處理 

return DefWindowProc(hwnd,uMsgId,wParam,lParam); 

在處理完消息後必須傳回0,這很重要,否則Windows将要不停地重試下去。對于那些在程式中不準備處理的消息,視窗過程會把它們都扔給 DefWindowProc進行預設處理,而且還要傳回那個函數的傳回值。在消息傳遞層次中,可以認為DefWindowProc函數是最頂層的函數。該函數發出WM_SYSCOMMAND消息,由系統執行Windows環境中多數視窗所公用的各種通用操作,如更新視窗的正文标題等等。在MFC下可以用下述部分代碼實作與上述SDK代碼相同的功能: 

BEGIN_MESSAGE_MAP(CTEMMSView, CFormView) 

//{ { AFX_MSG_MAP(CTEMMSView) 

ON_WM_LBUTTONDOWN() 

ON_WM_TIMER() 

//} } AFX_MSG_MAP 

END_MESSAGE_MAP() 

小結:Windows環境提供有非常豐富的系統資源,在這個基礎上可以編制出能滿足各種各樣目标功能的應用系統。要深入Windows程式設計就必須首先對Windows系統的運作機理有很好的認識,本文僅針對Windows的一種重要運作機制--消息機制作了較深入的剖析和闡述。對培養在Windows 下的程式設計思想有一定的幫助。對某些相關問題的詳細論述可以參考MSDN線上幫助的" SDK Reference" 部分。