1. 窗口過程
每一個窗口會有一個稱爲窗口過程的回調函數(WndProc),它帶有四個參數,分別爲:窗口句柄(Window Handle),消息ID(Message ID),和兩個消息參數(wParam, lParam), 當窗口收到消息時系統就會調用此窗口過程來處理消息。(因此叫回調函數) html
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:直接把消息送到窗口過程處理, 處理完了才返回。 app
6 GetMessage, PeekMessage
PeekMessage會當即返回 能夠保留消息
GetMessage在有消息時返回 會刪除消息 ide
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能夠對以上系統組件發送消息。 post
1、引言
隨着Windows操做系統的不斷推廣,衆多軟件開發包都提供有開發基於Windows平臺應用軟件的功能。雖然這些開發包不盡相同,流行的有Visual C++、Visual Basic、Delphi、C++ Builder 等多種,但由這些不一樣語言開發的軟件有一點倒是相同的--都是運行於Windows 操做平臺,都必須接受Windows 的運行機制。做爲Windows 操做系統靈魂的消息機制也就必然爲衆多用不一樣語言開發的Windows操做系統下運行的應用程序所接受。所以,要編寫深刻的Windows程序,就必須對 Windows的運行機制有很好的認識和理解。本文下面將對Windows操做系統下的消息運行機制作較爲深刻的剖析。
2、Windows事件驅動機制
咱們當中很多使用VC、Delphi等做爲開發語言的程序員是一步步從DOS下的Basic、C++中走過來的,並且大多在剛開始學習編程時也是先從 DOS下的編程環境入手的,所以在習慣了DOS下的過程驅動形式的順序程序設計方法後,每每在向Windows下的開發環境轉型的過程當中會對 Windows所採起的事件驅動方式感到沒法適應。由於DOS和Windows這兩種操做系統的運行機制是大相徑庭的,DOS下的任何程序都是使用順序的、過程驅動的程序設計方法。這種程序都有一個明顯的開始、明顯的過程以及一個明顯的結束,所以經過程序就能直接控制程序事件或過程的所有順序。即便是在處理異常時,處理過程也仍然是順序的、過程驅動的結構。而Windows的驅動方式則是事件驅動的,即程序的流程不是由事件的順序來控制,而是由事件的發生來控制,全部的事件是無序的,所爲一個程序員,在編寫程序時,並不知道用戶會先按下哪一個按紐,也就不知道程序先觸發哪一個消息。所以咱們的主要任務就是對正在開發的應用程序要發出的或要接收的消息進行排序和管理。事件驅動程序設計是密切圍繞消息的產生與處理而展開的,一條消息是關於發生的事件的消息。
3、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結構的地址,第二個參數表示窗口句柄,通常將其設置爲空,表示要獲取該應用程序建立的全部窗口的消息;第3、四參數用於指定消息範圍。後面三個參數被設置爲默認值,用於接收發送到屬於這個應用程序的任何一個窗口的全部消息。在接收到除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進行消息循環。
4、對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" 部分。 學習
轉自:http://www.cnblogs.com/railgunman/archive/2010/12/10/1902446.html