1. 程式人生 > >MFC訊息對映筆記

MFC訊息對映筆記

大家有沒有思考過當一個訊息出現,應用程式框架是如何將訊息與物件建立關係的?

1.訊息巨集

為了支援訊息對映,MFC提供了3種巨集。

1.1訊息對映的宣告和分解巨集

訊息對映的宣告和分界巨集包含在CCmdTarget類中,如下表:
這裡寫圖片描述
就是這3個巨集組織了一張龐大的訊息對映網路。所有繼承與CCmdTarget類的派生類均具有這種特性。下面分別說說這個巨集

1)DECALRE_MESSAGE_MAP巨集

這個巨集的定義在AfxWin.h檔案中定義,原始碼如下:


#define DECLARE_MESSAGE_MAP() \
protected: \
    static
const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #define BEGIN_TEMPLATE_MESSAGE_MAP(theClass, type_name, baseClass) \ PTM_WARNING_DISABLE \ template < typename type_name > \ const
AFX_MSGMAP* theClass< type_name >::GetMessageMap() const \ { return GetThisMessageMap(); } \ template < typename type_name > \ const AFX_MSGMAP* PASCAL theClass< type_name >::GetThisMessageMap() \ { \ typedef
theClass< type_name > ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE

可以看出,DECLARE_MESSAGE_MAP巨集主要定義了一個長度不定的靜態陣列變數_messageEntries[]、一個靜態變數messageMap和一個虛擬函式GetMessageMap。靜態陣列_messageEntries[]定義了一張訊息對映表,表中每一項指定了類或物件的訊息和處理此訊息的函式之間的對應關係,代表一條對映,可以用AFX_MSGMAP_ENTRY結構描述。
我們看到了一個陌生的型別 AFX_MSGMAP ,檢視其定義:
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};

這個結構體第一個成員是一個函式指標,第二個成員型別為 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)
};

從上述結構可以看出,每條對映有兩部分內容:前4個成員是關於訊息ID的,後兩個成員是關於訊息對應的執行函式的。其中,nSig成員用來標誌不同原型的訊息函式,MFC根據nSig的不同把訊息派發給對應的函式進行處理。而pfn成員則是一個指向CCmdTarget成員函式的指標,函式指標的型別定義如下:typedef void (AFX_MSG_CALL CCmdTarget::AFX_PMSG)(void)
當初始化訊息對映表時,各種型別的訊息函式都被轉換成相同的型別,而實際執行時,根據nSig把pfn還原成相應型別的訊息處理函式,並執行它。
另一個AFX_MSGMAP型別成員變數messageMap,是一個包含訊息對映的變數,把訊息對映的資訊和相關函式打包在一起,其結構定義如下:

struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//指向_GetBaseMessageMap函式
const AFX_MSGMAP_ENTRY* lpEntries;//儲存基類訊息對映入口_messageEntries的地址。
};

可見,AFX_MSGMAP實際上定義了一單項鍊表,連結串列中的每一項是一個指向訊息對映表的指標。其主要作用是獲取基類和本身的訊息對映入口地址。於是我們知道MFC首先把所有訊息一條條填入到AFX_MSGMAP_ENTRY結構中去,形成一個數組,該陣列存放了所有訊息和他們相關的引數,在通過AFX_MSGMAP獲得該陣列的首地址及基類的訊息對映入口地址。當本身對該訊息不響應的時候,就呼叫其基類的訊息響應,因此,基類的訊息處理函式就是派生類的預設訊息處理函式。

2).BEGIN_MESSAGE_MAP和END_MESSAGE_MAP巨集

這兩個巨集的定義也在AfxWin.h檔案中,程式碼如下:

#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[] = /
      { /
實際應用BEGIN_MESSAGE_MAP(myview,CView)等價於
      const AFX_MSGMAP* myview::GetMessageMap() const /
            { return & myview::messageMap; } /
      AFX_COMDAT AFX_DATADEF const AFX_MSGMAP myview::messageMap = /
      { & CView::messageMap, & myview::_messageEntries[0] }; /
      AFX_COMDAT const AFX_MSGMAP_ENTRY myview::_messageEntries[] = /
      { /
END_MESSAGE_MAP和BEGIN_MESSAGE_MAP是成對出現的
#define END_MESSAGE_MAP() /
            {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /
      }; /

可以看出,DECLARE_MESSAGE_MAP巨集在其類中申請了一個全域性結構和獲得該結構的函式,而在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間填寫剛才的全域性結構,將訊息和對應的函式聯絡起來,並通過AFX_MSGMAP中的pBaseMap指標,將各類按繼承順序連線起來,從而提供訊息流動的道路。

2.訊息對映巨集

除了上面的3個巨集外,常用的巨集還有:
這裡寫圖片描述
最常用的ON_COMMAND巨集定義如下:

#define ON_CONMMAND(id,memberFxn)
{
      WM_COMMAND,CN_COMMAND,(WORD)id,(WORD)id,AfxSig_vv,
      (AFX_PMSG)&OnFileNew}

根據上面的定義,ON_COMMAND(ID_FILE_NEW,OnFileNew),將被預編譯器展開為如下程式碼:

{WM_COMMAND,CN_COMMAND,(word)ID_FILE_NEW,(WORD)ID_FILE_NEW,AfxSig_vv,(AFX_PMSG)&OnFileNew},

雖然訊息對映巨集很重要,但是通常不需要使用者直接使用它們。當使用ClassWizzard把訊息處理函式與訊息關聯在一起的時候,他會將原始檔中自動建立訊息對映入口,所以就算讀者沒有理解上面的東西,會使用ClassWizard訊息就可以。

3.訊息路由傳動

大部分訊息流動是在使用者與應用程式之間進行的,一個訊息一般針對一類型別物件。MFC中CWinApp類的Run函式負責把訊息從應用程式的訊息佇列中取出,傳送到應用程式視窗函式WinProc中。該函式根據訊息的類別,再傳送到響應的物件中。
任何派生於CCmdTarget類的物件都能接受命令訊息。這些類物件組成一個有序連結串列,連結串列中的每一個
物件都可以同時接收到命令訊息,命令訊息按照一定的路徑傳送。連結串列中各個物件處理訊息的優先順序和順序並不相同。下面以例項分析一下:
建一個基於MFC的單文件應用程式,執行後,點選幫助選單:
這裡寫圖片描述
會彈出一個對話方塊,我們簡略分析一下訊息對映,首先將檢視切換到ClassView,雙擊MFC_DISCOVER.h檔案,有如下程式碼:

class CMFC_DISCOVERApp : public CWinApp
{
public:
    CMFC_DISCOVERApp();


// 重寫
public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();

// 實現
    afx_msg void OnAppAbout();  //關於對話方塊
    DECLARE_MESSAGE_MAP()    //訊息巨集對映申明
};

extern CMFC_DISCOVERApp theApp;

巨集DECLARE_MESSAGE_MAP聲明瞭訊息對映,並初始化訊息對映表。
再開啟MFC_DISCOVER類的實現檔案MFC_DISCOVER.cpp,觀察下面的程式碼:

BEGIN_MESSAGE_MAP(CMFC_DISCOVERApp, CWinApp)
    ON_COMMAND(ID_APP_ABOUT, &CMFC_DISCOVERApp::OnAppAbout)
    // 基於檔案的標準文件命令
    ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
    // 標準列印設定命令
    ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
``
當windows接收到ID_APP_ABOUT訊息時,通過查詢訊息對映表找到相應的處理函式OnAppAbout來相應訊息:

// 用於執行對話方塊的應用程式命令
void CMFC_DISCOVERApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

`
你若將
ON_COMMAND(ID_APP_ABOUT, &CMFC_DISCOVERApp::OnAppAbout)`註釋,則無法出現關於對話方塊了。