Windows訊息機制要點

來源:互聯網
上載者:User

標籤:解釋   tag   cformview   運行機制   post   快速鍵   system   提高   軟體開發工具   

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" 部分。

Windows訊息機制要點

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.