1. 程式人生 > >MFC的訊息處理機制及相關的巨集

MFC的訊息處理機制及相關的巨集

這段話是我在中文維基的“類成員函式指標”詞條寫的:

MFC類體系中,Windows訊息傳遞處理機制是基於CCmdTarget類及其派生類的靜態資料成員與靜態成員函式GetThisMessageMap()。使用者所寫的類中的Windows訊息處理函式(例如OnCommand)必須轉換為CCmdTarget::*的成員函式指標型別AFX_PMSG,儲存在該使用者類的_messageEntries靜態陣列中。

typedef void (CCmdTarget::*AFX_PMSG)(void);

呼叫使用者類中該訊息處理函式時,根據該函式儲存在_messageEntries中的signature(一個無符號整型表示的函式的形參型別列表與返回值型別),把型別為void (CCmdTarget::*AFX_PMSG)(void)的成員函式指標強制轉為其它型別的CCmdTarget成員函式指標(例如void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int),目前在union MessageMapFunctions中列出了近百種CCmdTarget成員函式指標),然後呼叫轉換後的成員函式指標。這是基於Visual C++編譯器把單繼承的成員函式指標編譯為只儲存了函式的記憶體起始地址,因此可以在同一個單繼承類中把一種型別的成員函式指標強制轉換為另一種成員函式指標,或者把單繼承派生類的成員函式指標強制轉換為基類成員函式指標。這是打破了C++標準的違例辦法。例如,對於CWnd::OnCommand函式,轉換過程是:

BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()

標頭檔案中的DECLARE_MESSAGE_MAP()

該巨集實際上增加了兩個靜態資料成員、一個虛擬函式:

static AFX_MSGMAP_ENTRY _messageEntries[]; 

static AFX_MSGMAP messageMap;

virtual AFX_MSGMAP*GetMessageMap()const; 


原始檔中的BEGIN_MESSAGE_MAP與END_MESSAGE_MAP

這兩個巨集實際上定義為:

#define BEGIN_MESSAGE_MAP(class_name,base_class)\ 
               AFX_MSGMAP*class_name::GetMessageMap()const\ 
                                 {return &class_name::message;}\ 
               AFX_MSGMAP messageMap=\ 
                                 {&base_class::messageMap,class_name::_messageEntries}\ 
               AFX_MSGMAP_ENTRY _messageEntries[]=\ 
                                 { 

#define ON_COMMAND(id,memFunc)\
              {WM_COMMAND,0,id,id,AFx_sig_vv,(AFX_PMSG)memFunc }, 

#define END_MESSAGE_MAP()\ 
                  {0,0,0,0,AfxSig_end,(AFX_PMSG)0}\ 
              };
 

注意這三個巨集中的內容共同完成了_messageEntries結構體陣列的填寫。

相關的資料結構

struct AFX_MSGMAP { 
          AFX_MSGMAP *pBaseMessageMap;//指向基類的本結構。
          AFX_MSGMAP_ENTRY*lpEntries;//指向本類的訊息對映表。
}; 

struct AFX_MSGMAP_ENTRY //該結構體可以儲存一條訊息的所有相關資訊。 
{ 
	UINT nMessage; //訊息ID
	UINT nCode; //控制元件通知程式碼,對於視窗訊息該值為0。處理命令訊息和控制元件通知的函式使用
	UINT nID; //命令ID,對於視窗訊息該值為0
	UINT nLastID; //是以nID開始的命令ID範圍內的最後一個命令ID,對於視窗訊息該值為0。處理命令訊息的函式能夠處理某一範圍的ID
	UINT nSig; //訊息處理函式時成員函式指標型別,其具體的型別資訊(signature)的代號
	AFX_PMSG pfn; //成員函式指標型別 typedef void (CCmdTarget::*AFX_PMSG)(void); 
}; 

高版本MFC的相關資料結構的新變化


在高版本MFC中(如VC++2013),類中僅定義一個靜態程雨函式GetThisMessageMap,一個虛擬函式GetMessageMap。在類的實現原始檔中,則定義兩個在類靜態成員函式GetThisMessageMap內部的靜態資料物件_messageEntries與messageMap

結構AFX_MSGMAP的第一個資料成員也改為一個函式指標用來返回基類的AFX_MSGMAP。原來老版本是用這個資料結構形成一個從最派生類開始的單向連結串列。

訊息的搜尋過程(對映機制)

一般搜尋過程

函式AfxWndProc接收Windows作業系統傳送的訊息。

函式AfxWndProc呼叫函式AfxCallWndProc進行訊息處理,這裡一個進步是把對控制代碼的操作轉換成對CWnd物件的操作。

函式AfxCallWndProc呼叫CWnd類的方法WindowProc進行訊息處理。


方法WindowProc呼叫方法OnWndMsg進行正式的訊息處理,通過函式AfxFindMessageEntry來搜尋各個類中的AFX_MSGMAP_ENTRY陣列(從最派生類開始向基類)找到匹配的訊息處理函式。大多數的預設訊息處理是呼叫CWnd的Default()方法。 而Default()方法所做的工作就是呼叫DefWindowProc對訊息進行處理。

一般的視窗訊息搜尋過程:

 

WM_COMMAND的搜尋過程

WalkPreTranslateTree和PreTranslateMessage

利用MFC框架生成的程式,都是從CWinApp開始執行的,而CWinapp實際繼承了CWinThread類。在CWinThread的執行過程中會呼叫視窗類中的WalkPreTranslateTree方法。而WalkPreTranslateTree方法實際上就是從當前視窗開始查詢願意進行訊息翻譯的類,直到找到視窗沒有父類為止。在WalkPreTranslateTree方法中呼叫了PreTranslateMessage方法。實際上PreTranslateMessage最大的好處是我們在訊息處理前可以在這個方法裡面先做一些事情。

例如希望在一個CEdit物件裡,把所有的輸入的字母都以大寫的形式出現。只需要在PreTranslateMessage方法中判斷message是否為WM_CHAR,如果是的話,把wParam(表示鍵值)由小寫字母的值該為大寫字母的值就實現了這個功能。