透過宏定義瞭解MFC的消息響應機制

作者:蘇林 發表時間:2008-04-13
本文來自: 天府書城。詳細出處參考:http://www.tianfubook.com/tianfubook_Article/tianfubook_Article.asp?page=1898

 

 

      消息系統是MFC的重要組成部分。MFC的消息響應機制並不複雜,而且MFC的開發環境 Visual Studio來供了非常好的自動化工具,自動生成代碼。MFC也定義了豐富的宏來簡化消息響應的代碼。這使得很多初學者都能快速開發出基於消息響應機制的應用程序。然後也正是這些帶來方便的宏,使很多人摸不着頭腦。當不小心的代碼操作使自動化工具不好用的時候,看着那一組更像是一堆的宏,很多人只好一頭霧水。
      要想真正瞭解MFC的消息機制,必需弄清楚這些宏。好在源碼面前無祕密,我們將從這些宏的源碼着手,逐步分析、瞭解、並學習MFC的消息響應及映射機制。
      第一個宏:DECLARE_MESSAGE_MAP()
      作用:爲一個消息響應類聲明必需的成員變量和成員函數。
      我們在窗口類、應用程序類、文檔類、視圖類、以及這些類的子類的定義中,都能看到DECLARE_MESSAGE_MAP()宏,通常被自動化工具聲明在類的最後部分,如:
// 生成的消息映射函數
protected:
     DECLARE_MESSAGE_MAP()
};
DECLARE_MESSAGE_MAP()
     宏定義如下(在DLL類型和WINDOWS程序類型下,定義會有不同,本文只分析非DLL類型,下同):
#define DECLARE_MESSAGE_MAP()
private:
     static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
     static const AFX_MSGMAP messageMap;
     virtual const AFX_MSGMAP* GetMessageMap() const;

      可以看到,宏DECLARE_MESSAGE_MAP()定義了兩個靜態成員變量,並重載了一個虛函數。下面分析一下這三個成員:
_messageEntries被定義爲一個AFX_MSGMAP_ENTRY類型的數組。結構體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_PTR nSig;       // signature type (action) or pointer to message #
     AFX_PMSG pfn;    // routine to call (or special value)
};
      通過查看源代碼中的註釋,可以看出AFX_MSGMAP_ENTRY定義了一個消息入口,或者說定義了一個消息到函數的映射關係。nMessage和nCode確定一條消息的內容,nID和nLastID確定了一條消息的來源,而nSig和pfn確定了消息的響應函數和調用方式。
      通過對消息響應過程的源碼分析可知,nSig事實上是一系列編碼,每一種編碼代表一種響應函數的類型,包括返回值、參數信息等。在響應消息的時候,將把pfn指向的函數指針強制類型轉換爲nSig代表的函數類型,然後再調用。
pfn的類型AFX_PMSG定義如下,意爲CCmdTarget的成員函數:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
      由此我們可以得出:靜態成員_messageEntries爲是一個消息到函數的映射表,或叫消息入口表。通過查找此表,可以找到消息的響應函數。
DECLARE_MESSAGE_MAP()宏聲明的另一個靜態成員變量messageMap被定義爲AFX_MSGMAP類型。AFX_MSGMAP定義如下:
struct AFX_MSGMAP
{
#ifdef _AFXDLL
     const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
     const AFX_MSGMAP* pBaseMap;
#endif
     const AFX_MSGMAP_ENTRY* lpEntries;
};
     過濾掉_AFXDLL(當MFC工程是以動態鏈接庫爲目標代碼編譯時,使用_AFXDLL宏)的影響,可以簡化爲如下:
struct AFX_MSGMAP
{
     const AFX_MSGMAP* pBaseMap;
     const AFX_MSGMAP_ENTRY* lpEntries;
};
      可見結構體AFX_MSGMAP中定義了兩個指針,pBaseMap指向另一個AFX_MSGMAP,lpEntries指向一個消息入口表。可以推想,在響應消息時,一定是在lpEntries指向的消息入口表中尋找響應函數,也可能會在pBaseMap指向的結構體中做同樣的響應函數尋找操作。
      至於DECLARE_MESSAGE_MAP()宏重載的虛函數GetMessageMap,可以猜測只是用來返回成員messageMap的地址而已。因爲GetMessageMap是虛函數,所以系統只要通過調用消息響應類的基類CCmdTarget類的GetMessageMap函數,便可以找到最後一級子類的消息映射信息。我們將在接下來對其它幾個宏的分析中得到相同的結論。
      第二個重要的宏:BEGIN_MESSAGE_MAP
     作用:定義DECLARE_MESSAGE_MAP宏聲明的靜態變量。
BEGIN_MESSAGE_MAP定義的源代碼如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass)
     const AFX_MSGMAP* theClass::GetMessageMap() const
         { return &theClass::messageMap; }
     AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
     { &baseClass::messageMap, &theClass::_messageEntries[0] };
     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
     {
      BEGIN_MESSAGE_MAP宏有兩個參數,theClass表示爲當前類,bassClass爲當前類的父類。
      BEGIN_MESSAGE_MAP宏首先定義了函數GetMessageMap的函數體,如前文所述,直接返回當前類的成員變量messageMap的地址。
const AFX_MSGMAP* theClass::GetMessageMap() const
         { return &theClass::messageMap; } 然後初始化了當前類的成員變量messageMap。messageMap的pBaseMap指針指向其父類的messageMap成員,lpEntries指針指向當前類的_messageEntries數組的首地址。
AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
     { &baseClass::messageMap, &theClass::_messageEntries[0] };
      最後,定義了_messageEntries數組初始化代碼的開始部分。
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
     {

      第三個宏END_MESSAGE_MAP()
      作用:定義_messageEntries數組初始化代碼的結束部分。
#define END_MESSAGE_MAP()
         {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
     };
      在DECLARE_MESSAGE_MAP和END_MESSAGE_MAP之間還有一些宏,如ON_COMMAND、ON_WM_CREATE等,這些宏最終都會被生成一條AFX_MSGMAP_ENTRY結構體數據,併成爲_messageEntries消息映射表數據的一個元素。我們以常見的ON_COMMAND宏爲例。ON_COMMAND宏的源代碼爲:
#define ON_COMMAND(id, memberFxn)
     { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v,
         static_cast<AFX_PMSG> (memberFxn) },

      通過以上分析,我們可以得到一個鏈表式的數據結構,子類的messageMap成員爲鏈表的頭節點。鏈表的每個節點都包含一個消息入口表。MFC的消息系統的標準備消息處理函數CCmdTarget::OnCmdMsg正是通過這樣一個鏈表查找到消息的響應函數,並調用該函數來響應消息。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章