消息映射的實現
Windows消息概述
Windows應用程序的輸入由Windows系統以消息的形式發送給應用程序的窗口。這些窗口通過窗口過程來接收和處理消息,然後把控制返還給Windows。
消息的分類
隊列消息和非隊列消息
從消息的發送途徑上看,消息分兩種:隊列消息和非隊列消息。隊列消息送到系統消息隊列,然後到線程消息隊列;非隊列消息直接送給目的窗口過程。
這裡,對消息隊列闡述如下:
Windows維護一個系統消息隊列(System message queue),每個GUI線程有一個線程消息隊列(Thread message queue)。
鼠標、鍵盤事件由鼠標或鍵盤驅動程序轉換成輸入消息並把消息放進系統消息隊列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次從系統消息隊列移走一個消息,確定它是送給哪個窗口的和這個窗口是由哪個線程創建的,然後,把它放進窗口創建線程的線程消息隊列。線程消息隊列接收送給該線程所創建窗口的消息。線程從消息隊列取出消息,通過Windows把它送給適當的窗口過程來處理。
除了鍵盤、鼠標消息以外,隊列消息還有WM_PAINT、WM_TIMER和WM_QUIT。
這些隊列消息以外的絕大多數消息是非隊列消息。
系統消息和應用程序消息
從消息的來源來看,可以分為:系統定義的消息和應用程序定義的消息。
系統消息ID的范圍是從0到WM_USER-1,或0X80000到0XBFFFF;應用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范圍的消息由應用程序自己使用;0XC000到0XFFFF范圍的消息用來和其他應用程序通信,為了ID的唯一性,使用::RegisterWindowMessage來得到該范圍的消息ID。
消息結構和消息處理
消息的結構
為了從消息隊列獲取消息信息,需要使用MSG結構。例如,::GetMessage函數(從消息隊列得到消息並從隊列中移走)和::PeekMessage函數(從消息隊列得到消息但是可以不移走)都使用了該結構來保存獲得的消息信息。
MSG結構的定義如下:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
該結構包括了六個成員,用來描述消息的有關屬性:
接收消息的窗口句柄、消息標識(ID)、第一個消息參數、第二個消息參數、消息產生的時間、消息產生時鼠標的位置。
應用程序通過窗口過程來處理消息
如前所述,每個“窗口類”都要登記一個如下形式的窗口過程:
LRESULT CALLBACK MainWndProc (
HWND hwnd,// 窗口句柄
UINT msg,// 消息標識
WPARAM wParam,//消息參數1
LPARAM lParam//消息參數2
)
應用程序通過窗口過程來處理消息:非隊列消息由Windows直接送給目的窗口的窗口過程,隊列消息由::DispatchMessage等派發給目的窗口的窗口過程。窗口過程被調用時,接受四個參數:
a window handle(窗口句柄);
a message identifier(消息標識);
two 32-bit values called message parameters(兩個32位的消息參數);
需要的話,窗口過程用::GetMessageTime獲取消息產生的時間,用::GetMessagePos獲取消息產生時鼠標光標所在的位置。
在窗口過程裡,用switch/case分支處理語句來識別和處理消息。
應用程序通過消息循環來獲得對消息的處理
每個GDI應用程序在主窗口創建之後,都會進入消息循環,接受用戶輸入、解釋和處理消息。
消息循環的結構如下:
while (GetMessage(&msg, (HWND) NULL, 0, 0)) {//從消息隊列得到消息
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg); //發送消息
}
}
消息循環從消息隊列中得到消息,如果不是快捷鍵消息或者對話框消息,就進行消息轉換和派發,讓目的窗口的窗口過程來處理。
當得到消息WM_QUIT,或者::GetMessage出錯時,退出消息循環。
MFC消息處理
使用MFC框架編程時,消息發送和處理的本質也如上所述。但是,有一點需要強調的是,所有的MFC窗口都使用同一窗口過程,程序員不必去設計和實現自己的窗口過程,而是通過MFC提供的一套消息映射機制來處理消息。因此,MFC簡化了程序員編程時處理消息的復雜性。
所謂消息映射,簡單地講,就是讓程序員指定要某個MFC類(有消息處理能力的類)處理某個消息。MFC提供了工具ClassWizard來幫助實現消息映射,在處理消息的類中添加一些有關消息映射的內容和處理消息的成員函數。程序員將完成消息處理函數,實現所希望的消息處理能力。
如果派生類要覆蓋基類的消息處理函數,就用ClassWizard在派生類中添加一個消息映射條目,用同樣的原型定義一個函數,然後實現該函數。這個函數覆蓋派生類的任何基類的同名處理函數。
下面幾節將分析MFC的消息機制的實現原理和消息處理的過程。為此,首先要分析ClassWizard實現消息映射的內幕,然後討論MFC的窗口過程,分析MFC窗口過程是如何實現消息處理的。
消息映射的定義和實現
MFC處理的三類消息
根據處理函數和處理過程的不同,MFC主要處理三類消息:
Windows消息,前綴以“WM_”打頭,WM_COMMAND例外。Windows消息直接送給MFC窗口過程處理,窗口過程調用對應的消息處理函數。一般,由窗口對象來處理這類消息,也就是說,這類消息處理函數一般是MFC窗口類的成員函數。
控制通知消息,是控制子窗口送給父窗口的WM_COMMAND通知消息。窗口過程調用對應的消息處理函數。一般,由窗口對象來處理這類消息,也就是說,這類消息處理函數一般是MFC窗口類的成員函數。
需要指出的是,Win32使用新的WM_NOFITY來處理復雜的通知消息。WM_COMMAND類型的通知消息僅僅能傳遞一個控制窗口句柄(lparam)、控制窗ID和通知代碼(wparam)。WM_NOTIFY能傳遞任意復雜的信息。
命令消息,這是來自菜單、工具條按鈕、加速鍵等用戶接口對象的WM_COMMAND通知消息,屬於應用程序自己定義的消息。通過消息映射機制,MFC框架把命令按一定的路徑分發給多種類型的對象(具備消息處理能力)處理,如文檔、窗口、應用程序、文檔模板等對象。能處理消息映射的類必須從CCmdTarget類派生。
在討論了消息的分類之後,應該是討論各類消息如何處理的時候了。但是,要知道怎麼處理消息,首先要知道如何映射消息。
MFC消息映射的實現方法
MFC使用ClassWizard幫助實現消息映射,它在源碼中添加一些消息映射的內容,並聲明和實現消息處理函數。現在來分析這些被添加的內容。
在類的定義(頭文件)裡,它增加了消息處理函數聲明,並添加一行聲明消息映射的宏DECLARE_MESSAGE_MAP。
在類的實現(實現文件)裡,實現消息處理函數,並使用IMPLEMENT_MESSAGE_MAP宏實現消息映射。一般情況下,這些聲明和實現是由MFC的ClassWizard自動來維護的。看一個例子:
在AppWizard產生的應用程序類的源碼中,應用程序類的定義(頭文件)包含了類似如下的代碼:
//{{AFX_MSG(CTttApp)
afx_msg void OnAppAbout();
/