進入MFC講壇的前言(四)
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
MFC的消息映射機制
MFC的設計者們在設計MFC時,緊緊把握一個目標,那就是盡可能使得MFC的代碼要小,速度盡可能快。為了這個目標,他們使用了許多技巧,其中很多技巧體現在宏的運用上,實現MFC的消息映射的機制就是其中之一。
同MFC消息映射機制有關的宏有下面幾個:
DECLARE_MESSAGE_MAP()宏
BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
弄懂MFC消息映射機制的最好辦法是將找出一個具體的實例,將這些宏展開,并找出相關的數據結構。
DECLARE_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()宏的定義如下:
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
從上面的定義可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
定義一個長度不定的靜態數組變量_messageEntries[];
定義一個靜態變量messageMap;
定義一個虛擬函數GetMessageMap();
在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中兩個對外不公開的數據結構
AFX_MSGMAP_ENTRY和AFX_MSGMAP。為了弄清楚消息映射,有必要考察一下這兩個數據結構的定義。
AFX_MSGMAP_ENTRY的定義
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
結構中各項的含義注釋已經說明得很清楚了,這里不再多述,從上面的定義你是否看出,AFX_MSGMAP_ENTRY結構實際上定義了消息和處理此消息的動作之間的映射關系。因此靜態數組變量_messageEntries[]實際上定義了一張表,表中的每一項指定了相應的對象所要處理的消息和處理此消息的函數的對應關系,因而這張表也稱為消息映射表。再看看AFX_MSGMAP的定義。
(2)AFX_MSGMAP的定義
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
不難看出,AFX_MSGMAP定義了一單向鏈表,鏈表中每一項的值是一指向消息映射表的指針(實際上就是_messageEntries的值)。通過這個鏈表,使得在某個類中調用基類的的消息處理函數很容易,因此,“父類的消息處理函數是子類的缺省消息處理函數”就“順理成章”了。在后面的“MFC窗口的消息處理”一節中會對此作詳細的講解。
BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
它們的定義如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
對應BEGIN_MESSAGE_MAP()的定義可能不是一下子就看得明白,不過不要緊,舉一例子就很清楚了。對于BEGIN_MESSAGE_MAP(CView, CWnd),VC預編譯器將其展開成下面的形式:
const AFX_MSGMAP* CView::GetMessageMap() const
{
return &CView::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
{
&CWnd::messageMap,
&CView::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
至于END_MESSAGE_MAP()則不過定義了一個表示映射表結束的標志項,我想大家對于這種簡單的技巧應該是很熟悉的,無需多述。
到此為止,我想大家也已經想到了象ON_COMMAND這樣的宏的具體作用了,不錯它們只不過定義了一種類型的消息映射項,看看ON_COMMAND的定義:
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
根據上面的定義,ON_COMMAND(ID_FILE_NEW, OnFileNew)將被VC預編譯器展開
如下:
{WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
(AFX_PMSG)&OnFileNew},
到此,MFC的消息映射機制已經清楚了,現在提出并解答兩個問題以作為對這一節的小結。
為什么不直接使用虛擬函數實現消息處理函數呢?這是一個GOOD QUESTION。前面已經說過,MFC的設計者們在設計MFC時有一個很明確的目標,就是使得“MFC的代碼盡可能小,速度盡可能快”,如果采用虛擬函數,那么對于所有的窗口消息,都必須有一個與之對應的虛擬函數,因而對每一個從CWnd派生的類而言,都會有一張很大的虛擬函數表vtbl。但是在實際應用中,一般只對少數的消息進行處理,大部分都交給系統缺省處理,所以表中的大部分項都是無用項,這樣做就浪費了很多內存資源,這同MFC設計者們的設計目標是相違背的。當然,MFC所使用的方法只是解決這類問題的方式之一,不排除還有其他的解決方式,但就我個人觀點而言,這是一種最好的解決方式,體現了很高的技巧性,值得我們學習。
至于這第二個問題,是由上面的問題引申出來的。如果在子類和父類中出現了相同的消息出來函數,VC編譯器會怎么處理這個問題呢?VC不會將它們看作錯誤,而會象對待虛擬函數類似的方式去處理,但對于消息處理函數(帶afx_msg前綴),則不會生成虛擬函數表vtbl。RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
MFC的設計者們在設計MFC時,緊緊把握一個目標,那就是盡可能使得MFC的代碼要小,速度盡可能快。為了這個目標,他們使用了許多技巧,其中很多技巧體現在宏的運用上,實現MFC的消息映射的機制就是其中之一。
同MFC消息映射機制有關的宏有下面幾個:
DECLARE_MESSAGE_MAP()宏
BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
弄懂MFC消息映射機制的最好辦法是將找出一個具體的實例,將這些宏展開,并找出相關的數據結構。
DECLARE_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()宏的定義如下:
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
從上面的定義可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
定義一個長度不定的靜態數組變量_messageEntries[];
定義一個靜態變量messageMap;
定義一個虛擬函數GetMessageMap();
在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中兩個對外不公開的數據結構
AFX_MSGMAP_ENTRY和AFX_MSGMAP。為了弄清楚消息映射,有必要考察一下這兩個數據結構的定義。
AFX_MSGMAP_ENTRY的定義
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
結構中各項的含義注釋已經說明得很清楚了,這里不再多述,從上面的定義你是否看出,AFX_MSGMAP_ENTRY結構實際上定義了消息和處理此消息的動作之間的映射關系。因此靜態數組變量_messageEntries[]實際上定義了一張表,表中的每一項指定了相應的對象所要處理的消息和處理此消息的函數的對應關系,因而這張表也稱為消息映射表。再看看AFX_MSGMAP的定義。
(2)AFX_MSGMAP的定義
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
不難看出,AFX_MSGMAP定義了一單向鏈表,鏈表中每一項的值是一指向消息映射表的指針(實際上就是_messageEntries的值)。通過這個鏈表,使得在某個類中調用基類的的消息處理函數很容易,因此,“父類的消息處理函數是子類的缺省消息處理函數”就“順理成章”了。在后面的“MFC窗口的消息處理”一節中會對此作詳細的講解。
BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
它們的定義如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
對應BEGIN_MESSAGE_MAP()的定義可能不是一下子就看得明白,不過不要緊,舉一例子就很清楚了。對于BEGIN_MESSAGE_MAP(CView, CWnd),VC預編譯器將其展開成下面的形式:
const AFX_MSGMAP* CView::GetMessageMap() const
{
return &CView::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
{
&CWnd::messageMap,
&CView::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
至于END_MESSAGE_MAP()則不過定義了一個表示映射表結束的標志項,我想大家對于這種簡單的技巧應該是很熟悉的,無需多述。
到此為止,我想大家也已經想到了象ON_COMMAND這樣的宏的具體作用了,不錯它們只不過定義了一種類型的消息映射項,看看ON_COMMAND的定義:
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
根據上面的定義,ON_COMMAND(ID_FILE_NEW, OnFileNew)將被VC預編譯器展開
如下:
{WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
(AFX_PMSG)&OnFileNew},
到此,MFC的消息映射機制已經清楚了,現在提出并解答兩個問題以作為對這一節的小結。
為什么不直接使用虛擬函數實現消息處理函數呢?這是一個GOOD QUESTION。前面已經說過,MFC的設計者們在設計MFC時有一個很明確的目標,就是使得“MFC的代碼盡可能小,速度盡可能快”,如果采用虛擬函數,那么對于所有的窗口消息,都必須有一個與之對應的虛擬函數,因而對每一個從CWnd派生的類而言,都會有一張很大的虛擬函數表vtbl。但是在實際應用中,一般只對少數的消息進行處理,大部分都交給系統缺省處理,所以表中的大部分項都是無用項,這樣做就浪費了很多內存資源,這同MFC設計者們的設計目標是相違背的。當然,MFC所使用的方法只是解決這類問題的方式之一,不排除還有其他的解決方式,但就我個人觀點而言,這是一種最好的解決方式,體現了很高的技巧性,值得我們學習。
至于這第二個問題,是由上面的問題引申出來的。如果在子類和父類中出現了相同的消息出來函數,VC編譯器會怎么處理這個問題呢?VC不會將它們看作錯誤,而會象對待虛擬函數類似的方式去處理,但對于消息處理函數(帶afx_msg前綴),則不會生成虛擬函數表vtbl。RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成