天天看點

進入MFC講壇的前言(四)

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。

繼續閱讀