消息機制

計算機術語

在Windows中發生的一切都可以用消息來表示,消息用於告訴操作系統發生了什麼,所有的Windows應用程序都是消息驅動的。一個消息是由消息的名稱(UINT)和兩個參數(WPARAM, LPARAM)組成。消息的參數中包含有重要的信息。例如對滑鼠消息而言,LPARAM中一般包含滑鼠的位置信息,而WPARAM參數中包含了發生該消息時,SHIFT、CTRL等鍵的狀態信息,對於不同的消息類型來說,兩個參數也都相應地具有明確意義。

程序核心


在Windows中,不同的消息由應用程序的不同部分進行處理。MFC庫將很多底層的消息都屏蔽了,使用戶更加方便、簡易地處理消息。例如,用戶接收到諸如移動滑鼠鍵(WM_MOUSEMOVE)消息或單擊滑鼠鍵(WM_LRBUTTONDOWN)消息時不必處理窗口和滑鼠的重畫工作,MFC及應用程序框架會替用戶做這些工作。在使用MFC進行編程時,用戶只需處理一些高層的消息,例如,“用戶在單擊窗口中的OK按扭”,“用戶現在選中了下拉列表框中的第五項”等等,這樣就大大減輕了程序員的負擔。

輸入焦點


Windows是一個以消息為導向的系統,應用程序只能被動地等待用戶按鍵的消息,不能主動地去讀鍵盤的狀態,也就是說,每當鍵盤上有個鍵被按下,系統就會發出一個按鍵消息給窗口,告訴它某個鍵被按下去了,只要滑鼠移動一下,系統也會發出相應的消息,並把滑鼠的坐標信息傳給窗口。
Windows可以同時執行許多程序,但鍵盤只有一個,怎麼判斷由哪個窗口接收鍵盤及滑鼠的消息呢?採用“輸入焦點”(input focus)技術可以解決這個問題。只要某個窗口取得輸入焦點,它不但會被提升到屏幕的最前面,顏色也會有所不同,所有的鍵盤消息就會導向該窗口,該窗口也成為“活動窗口”。
窗口如何取得輸入焦點?通常被滑鼠單擊的窗口會得到輸入焦點,除此之外,程序本身也可以利用SetFocus()來指定哪個窗口擁有輸入焦點。
CWnd* CWnd::SetFocus();
如果調用某窗口的SetFocus()成員函數,該窗口就可以取得輸入焦點,該函數返回前一個擁有輸入焦點的窗口。
如果某個窗口的輸入焦點被搶走,Windows系統就會發出WM_KILLFOCUS消息給這個失去輸入焦點的窗口,同時還會告訴該窗口下一個取得輸入焦點的窗口的指針。而獲得輸入焦點的窗口則會收到WM_SETFOCUS消息。
消息響應函數分別為:
afx_msg void OnKillFocus(CWnd* pNewWnd);
其中的參數為失去輸入焦點的窗口的指針。
Afx_msg void OnSetFocus(CWnd* pOldWnd);
其中的參數為得到輸入焦點的窗口的指針。

消息分類


Windows系統預定義了許多消息,每個消息都擁有一個宏定義,即用形象的字元串來標識消息,一系列#define 語句將消息與特定數值聯繫起來,可以在頭文件WinUser.h中找到這些宏定義,例如
#define WM_PAINT 120
可以在程序中通過消息名“WM_PAINT”來訪問它。其他消息如:
#define WM_MOUSEMOVE 0x0200
#defineWM_LBUTTONDOWN 0x0201
#define WM_LBUTTONUP 0x0202
#defineWM_LBUTTONDBLCLK 0x0203
#defineWM_RBUTTONDOWN 0x0204
#define WM_RBUTTONUP 0x0205
#define WM_RBUTTONDBLCLK 0x0206
#define WM_MBUTTONDOWN 0x0207
#define WM_MBUTTONUP 0x0208
#define WM_MBUTTONDBLCLK 0x0209
系統定義的消息有不同的前綴,不同的前綴有不同的含義。

標準的消息

除了WM_COMMAND消息,所有以WM_為前綴的消息都是標準的Windows消息,如窗口、滑鼠移動、窗口大小改變等,程序啟動或退出甚至每一段固定的時間都會產生標準Windows消息。如
1)鍵盤消息
對於窗口而言,來自用戶的按鍵輸入可分為兩類,一類是系統鍵(system key),另一類則是非系統鍵。凡是ALT和其它鍵一同按下的組合稱為“系統鍵”,窗口收到系統鍵之後,會自動地將它解釋成系統事件,或者查閱鍵盤加速表,將系統鍵翻譯成加速表指定的信息。如:ALT+F4的組合會迫使窗口關閉,“ALT+字母”的組合可能會拉下某個菜單。
當用戶按下某個鍵時,Windows系統會先發出WM_KEYDOWN消息給窗口,這個消息的意思是“按鍵被壓下去”。接著Windows系統會發出WM_CHAR給同一個窗口,這個消息代表的意義是“系統送來某個字元”,如果用戶放開此鍵,Windows系統會發出WM_KEYUP消息,表示“按鍵被放開”。如果用戶一直按住某個鍵不放,經過一段時間之後會產生“連發”的效果,造成Windows系統不停地發出WM_KEYDOWN與WM_CHAR消息。
計算機內部以ASCII碼的規則來記錄所有的英文字母和數字元號。不過不是鍵盤上每個按鍵都可以對應成ASCII碼中的字元,如大小寫鍵、CTRL鍵、F1到F12鍵等。
每個按鍵都有對應的掃描碼,PC BIOS收到鍵盤的中斷消息后,會自動將掃描碼翻譯成ASCII碼,但有些控制鍵無法譯成ASCII碼,如Page UP、Page Down等。Windows定義了一套與硬體無關的“虛擬鍵碼”來表示鍵盤上所有的按鍵,如A鍵就是VK_A、ESC鍵就是VK_ESC、F1鍵是VK_F1、ALT鍵是VK_MENU等。因為“虛擬鍵碼”定義的規則與硬體無關,所以有些虛擬鍵在通常的鍵盤上根本就找不著。
#define VK_LBUTTON 0x01
#define VK_RBUTTON 0x02
#define VK_CANCEL 0x03
#define VK_MBUTTON 0x04
#define VK_BACK 0x08
#define VK_TAB 0x09
#define VK_CLEAR 0x0C
#define VK_RETURN 0x0D
#define VK_SHIFT 0x10
#define VK_CONTROL 0x11
#define VK_MENU 0x12
#define VK_PAUSE 0x13
#define VK_CAPITAL 0x14
#define VK_F1 0x70
#define VK_F2 0x71
#define VK_F3 0x72
#define VK_F4 0x73
#define VK_F5 0x74
#define VK_F6 0x75
#define VK_F7 0x76
#define VK_F8 0x77
#define VK_F9 0x78
#define VK_F10 0x79
#
#define WM_CHAR 0x0102 //字元消息
WM_CHAR也稱為鍵盤消息,如果某窗口擁有輸入焦點,當用戶在應用程序運行時按下一個鍵時,系統就會產生一個鍵盤消息WM_CHAR,告訴此窗口鍵盤上哪個鍵被按下了。該消息的處理函數為OnChar()。具體形式為:
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
各參數含義為:
nChar: 鍵盤所輸入的ASCII碼。
nRepCnt: 按鍵的重複次數,當用戶按下某個鍵不放時,該參數將持續增加。
nFlag: 用於傳遞按鍵的其它一些信息,如掃描碼,上一次按鍵狀態等。具體如下:
位元組 說明
0-7 鍵盤掃描碼
8 此按鍵為擴充按鍵,如F1,F12等功能鍵,此位元組等於1時為真
9-12 保留
13 此位元組為1表示按下鍵的同時,ALT鍵也被按住了
14 前一個按鍵狀態。此位元組為1代表信息在按鍵被按下之前就送出來了
15 此位元組為1表示這個按鍵已經被放開了,反之就表示還被按著
此外還有兩個常用的鍵盤消息:WM_KEYDOWN和WM_KEYUP.
WM_KEYDOWN消息是當用戶按下一個非系統鍵時產生的,非系統鍵就是不按下ALT鍵時的按鍵。
WM_KEYUP 消息是當用戶釋放一個非系統鍵時產生的。

滑鼠消息

① #define WM_MOUSEMOVE 0x0200 //滑鼠移動消息
當滑鼠在某個窗口內移動時,Windows會不斷地發出滑鼠移動消息WM_MOUSEMOVE,並把滑鼠的最新位置傳給該窗口。如果在窗口的範圍內按下滑鼠左鍵,系統就會發出“按下左鍵”的WM_LBUTTONDOWN消息給該窗口,等到用戶放開按鍵后,再發出“放開左鍵”的WM_LBUTTONUP消息給該窗口。
滑鼠移動消息的消息響應函數為:
afx_msg voidOnMouseMove(UINT nFlags, CPoint point)
其中的參數含義如下:
UINT nFlag:此事件發生時,滑鼠按鍵、鍵盤控制鍵的狀態,可以是以下值的任意組合:
當用戶按下CTRL鍵時,nFlags設置為MK_CONTROL。
當用戶按下滑鼠左鍵時,nFlags設置為MK_LBUTTON。
當用戶按下滑鼠中鍵時,nFlags設置為MK_MBUTTON

淺析消息機制

Windows系統是一個消息驅動的OS,什麼是消息呢?我很難說得清楚,也很難下一個定義,我下面從不同的幾個方面講解一下,希望大家看了後有一點了解。

消息的組成

一個消息由一個消息名稱(UINT),和兩個參數(WPARAM,LPARAM)組成。當用戶進行了輸入或是窗口的狀態發生改變時系統都會發送消息到某一個窗口。例如當菜單轉中之後會有WM_COMMAND消息發送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對菜單來講就是菜單ID。當然用戶也可以定義自己的消息名稱,也可以利用自定義消息來發送通知和傳送數據。

誰將收到消息

一個消息必須由一個窗口接收。在窗口的過程(WNDPROC)中可以對消息進行分析,對自己感興趣的消息進行處理。例如你希望對菜單選擇進行處理那麼你可以定義對WM_COMMAND進行處理的代碼,如果希望在窗口中進行圖形輸出就必須對WM_PAINT進行處理。

未處理的消息

M$為窗口編寫了默認的窗口過程,這個窗口過程將負責處理那些你不處理消息。正因為有了這個默認窗口過程我們才可以利用Windows的窗口進行開發而不必過多關注窗口各種消息的處理。例如窗口在被拖動時會有很多消息發送,而我們都可以不予理睬讓系統自己去處理。

窗口句柄

說到消息就不能不說窗口句柄,系統通過窗口句柄來在整個系統中唯一標識一個窗口,發送一個消息時必須指定一個窗口句柄表明該消息由那個窗口接收。而每個窗口都會有自己的窗口過程,所以用戶的輸入就會被正確的處理。例如有兩個窗口共用一個窗口過程代碼,你在窗口一上按下滑鼠時消息就會通過窗口一的句柄被發送到窗口一而不是窗口二。

示例

下面有一段偽代碼演示如何在窗口過程中處理消息
LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
switch(uMessageType)
{//使用SWITCH語句將各種消息分開
case(WM_PAINT):
doYourWindow(...);//在窗口需要重新繪製時進行輸出
break;
case(WM_LBUTTONDOWN):
doYourWork(...);//在滑鼠左鍵被按下時進行處理
break;
default:
callDefaultWndProc(...);//對於其它情況就讓系統自己處理
}
}
接下來談談什麼是消息機制:系統將會維護一個或多個消息隊列,所有產生的消息都會被放入或是插入隊列中。系統會在隊列中取出每一條消息,根據消息的接收句柄而將該消息發送給擁有該窗口的程序的消息循環。每一個運行的程序都有自己的消息循環,在循環中得到屬於自己的消息並根據接收窗口的句柄調用相應的窗口過程。而在沒有消息時消息循環就將控制權交給系統所以Windows可以同時進行多個任務。下面的偽代碼演示了消息循環的用法:
while(1)
{
id=getMessage(...);
if(id == quit)
break;
translateMessage(...);
}
當該程序沒有消息通知時getMessage就不會返回,也就不會佔用系統的CPU時間。圖示消息投遞模式
在16位的系統中系統中只有一個消息隊列,所以系統必須等待當前任務處理消息后才可以發送下一消息到相應程序,如果一個程序陷如死循環或是耗時操作時系統就會得不到控制權。這種多任務系統也就稱為協同式的多任務系統。Windows3.X就是這種系統。
而32位的系統中每一運行的程序都會有一個消息隊列,所以系統可以在多個消息隊列中轉換而不必等待當前程序完成消息處理就可以得到控制權。這種多任務系統就稱為搶先式的多任務系統。Windows95/NT就是這種系統。

消息隊列


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

消息循環


Windows為當前執行的每個Windows程序維護一個「消息隊列」。在發生輸入事件之後,Windows將事件轉換為一個「消息」並將消息放入程序的消息隊列中。程序通過執行一塊稱之為「消息循環」的程序代碼從消息隊列中取出消息:
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
msg變數是型態為MSG的結構,型態MSG在WINUSER.H中定義如下:
typedef struct tagMSG
{
HWND hwnd ;
UINT message ;
WPARAM wParam ;
LPARAM lParam ;
DWORD time ;
POINT pt ;
}
MSG, * PMSG ;
POINT數據型態也是一個結構,它在WINDEF.H中定義如下:
typedef struct tagPOINT
{
LONG x ;
LONG y ;
}
POINT, * PPOINT;
TranslateMessage(&msg); 將msg結構傳給Windows,進行一些鍵盤轉換。(關於這一點,我們將在第六章中深入討論。)
DispatchMessage(&msg);又將msg結構回傳給Windows。然後,Windows將該消息發送給適當的窗口消息處理程序,讓它進行處理。這也就是說,Windows將呼叫窗口消息處理程序。在HELLOWIN中,這個窗口消息處理程序就是WndProc函數。處理完消息之後,WndProc傳回到Windows。此時,Windows還停留在DispatchMessage呼叫中。在結束DispatchMessage呼叫的處理之後,Windows回到HELLOWIN程序中,並且接著從下一個GetMessage呼叫開始消息循環。

消息的分類


消息能夠被分為「隊列化的」和「非隊列化的」。

隊列化消息

隊列化的消息是由Windows放入程序消息隊列中的。在程序的消息循環中,重新傳回並分配給窗口消息處理程序。非隊列化的消息在Windows呼叫窗口時直接送給窗口消息處理程序。也就是說,隊列化的消息被「發送」給消息隊列,而非隊列化的消息則「發送」給窗口消息處理程序。任何情況下,窗口消息處理程序都將獲得窗口所有的消息--包括隊列化的和非隊列化的。窗口消息處理程序是窗口的「消息中心」。隊列化消息基本上是使用者輸入的結果,以擊鍵(如WM_KEYDOWN和WM_KEYUP消息)、擊鍵產生的字元(WM_CHAR)、滑鼠移動(WM_MOUSEMOVE)和滑鼠按鈕(WM_LBUTTONDOWN)的形式給出。隊列化消息還包含時鐘消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。

非隊列化消息

非隊列化消息則是其它消息。在許多情況下,非隊列化消息來自呼叫特定的Windows函數。例如,當WinMain呼叫CreateWindow時,Windows將建立窗口並在處理中給窗口消息處理程序發送一個WM_CREATE消息。當WinMain呼叫ShowWindow時,Windows將給窗口消息處理程序發送WM_SIZE和WM_SHOWWINDOW消息。當WinMain呼叫UpdateWindow時,Windows將給窗口消息處理程序發送WM_PAINT消息。鍵盤或滑鼠輸入時發出的隊列化消息信號,也能在非隊列化消息中出現。例如,用鍵盤或滑鼠選擇了一個菜單項時,鍵盤或滑鼠消息就是隊列化的,而說明菜單項已選中的WM_COMMAND消息則可能就是非隊列化的。

函數區別


SendMessage()與PostMessage()的區別
它們兩者是用於嚮應用程序發送消息的。PostMessagex()將消息直接加入到應用程序的消息隊列中,不等程序返回就退出;而SendMessage()則剛好相反,應用程序處理完此消息后,它才返回。我想下圖能夠比較好的體現這兩個函數的關係:
消息機制
消息機制
函數peekmessage和getmessage的區別
兩個函數主要有以下兩個區別:
1.GetMessage將等到有合適的消息時才返回,而PeekMessage只是撇一下消息隊列。
2.GetMessage會將消息從隊列中刪除,而PeekMessage可以設置最後一個參數wRemoveMsg來決定是否將消息保留在隊列中。