1. 程式人生 > >MFC訊息對映的原理

MFC訊息對映的原理

原文地址:http://www.cnblogs.com/lidabo/p/3694726.html

多型的實現機制有兩種,一是通過查詢絕對位置表,二是查詢名稱表;兩者各有優缺點,那麼為什麼mfc的訊息對映採用了第二種方法,而不是c++使用的第一種呢?因為在mfc的gui類庫是一個龐大的繼承體系,而裡面的每個類有很多成員函式(只說訊息反映相關的成員函式啊),而且在派生類中,需要改寫的也比較少(我用來做練習的程式就是那麼一兩個,呵呵)。那麼用c++的虛擬函式的實現機制會導致什麼問題呢?就是大量虛表的建立使得空間浪費掉很多。

嗯…怎麼辦呢?於是各大c++名庫(比如QT,MFC,VCL…)在訊息對映的實現方面,拋開了虛擬函式的方式,而用了第二種方法:查詢名稱表,其原理五花八門,各顯神通,讓我想到了春秋時代,各國諸侯置周天子不顧,挾天子令諸侯,各自為政的階段,呵呵~

現在先說MFC的做法:MFC訊息對映機制的原理,也就是MFC是怎麼做到一個訊息來了,就呼叫相應的成員函式的?(在VC程式設計裡面用的是訊息迴圈機制,那比較好理解,就是在訊息處理程式裡面來個大大的swicth…)

好了,在c++ stl成熟的現在,很多人都會想到用map來匹配(據我所知,有些公司很喜歡這樣用,拋開了mfc的做法),但是當時stl沒有流行,所以mfc設計者們就來個連結串列查詢。

先看用法:

首先,要用訊息處理的類,必須要繼承自CcmdTarget類;

然後,在類的宣告中有如下的巨集:DECLARE_MESSAGE_MAP()和需要實現的訊息對映

然後,在類的實現檔案中,有如下巨集:BEGIN_MESSAGE_MAP(…), … END_MESSAGE_MAP()

具體例子如下所示:

//標頭檔案中:

class CMainFrame : public CFrameWnd

{

……

// 生成的訊息對映函式

protected:

     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

     DECLARE_MESSAGE_MAP()

};

//實現檔案中:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

     ON_WM_CREATE()

END_MESSAGE_MAP()

那些巨集展開之後如下所示:

class CMainFrame : public CFrameWnd

{

……

// 生成的訊息對映函式

protected:

     int OnCreate(LPCREATESTRUCT lpCreateStruct);

// 下三行為巨集DECLARE_MESSAGE_MAP()的展開

protected:

     static const AFX_MSGMAP* PASCAL GetThisMessageMap();

     virtual const AFX_MSGMAP* GetMessageMap() const;

};

// 下10行為巨集BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)的展開

const AFX_MSGMAP* CmainFrame::GetMessageMap() const

{

return GetThisMessageMap();

}

const AFX_MSGMAP* CMainFrame::GetThisMessageMap()

{

     typedef CmainFrame ThisClass;

     typedef CframeWnd TheBaseClass;

     static const AFX_MSGMAP_ENTRY _messageEntries[] =

     {

         // 下2行為巨集ON_WM_CREATE()的展開

{ WM_CREATE, 0, 0, 0, AfxSig_is, (AFX_PMSG) (AFX_PMSGW)(static_cast

< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },

         // 下5行為巨集END_MESSAGE_MAP()的展開

{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }

     };

static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, _messageEntries[0] };

     return &messageMap;

}

上面這些幾乎全是資料結構的初始化程式碼,先不管這裡面的結構放的是什麼內容,先看類宣告那個巨集展開的兩個函式究竟在mfc框架的那個地方使用?(先看過程,在看資料結構,這是面向物件分析的不二法門~)

下面,說一下別人總結的mfc流程中的訊息分派,(參考文章會放在附錄裡面的,這只是筆記嘛,呵呵)

注意:我安裝的是VS2005,MFC原始碼在安裝的目錄下面的  VC/atlmfc/src/mfc目錄裡面

1、   先假定mfc的訊息入口點是CWnd::WindowProc函式(至於是如何流入的,請參考其他文章),其程式碼就好像vc程式設計裡面那個WinMain函式的訊息迴圈部分差不多:

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

    LRESULT lResult = 0;

    if (!OnWndMsg(message, wParam, lParam, &lResult))

        lResult = DefWindowProc(message, wParam, lParam);

    return lResult;

}

看,就是看一下該訊息在OnWndWsg裡是否找到對應的處理函式,如果沒找到,用DefWindowProc處理;

2、   那麼,這個OnWndWsg裡面就是呼叫以上巨集展開的那個函式來取得相關的訊息對映滴~簡化過後的程式碼如下:

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

{

    LRESULT lResult = 0;

    const AFX_MSGMAP* pMessageMap;

     //取得訊息對映結構,GetMessageMap為虛擬函式,所以實際取的是CmainFrame的訊息對映

    pMessageMap = GetMessageMap();

    // 查詢對應的訊息處理函式

    for (pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)

        if (message < 0xC000)

            if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)

                goto LDispatch;

    ... ...

LDispatch:

    //通過聯合來匹配正確的函式指標型別

    union MessageMapFunctions mmf;

mmf.pfn = lpEntry->pfn;

……

其中的pMessageMap = GetMessageMap();語句要留意,因為GetMessageMap是虛擬函式,在上面的CmainFrame類中已經重寫了,所以,呼叫的實際上是CmainFrame的GetMessageMap;返回一個表格的指標pMessageMap,然後根據訊息的型別在裡面尋找,這個表格的資料結構如下:

struct AFX_MSGMAP

{

     const AFX_MSGMAP* (* pfnGetBaseMap)();

     const AFX_MSGMAP_ENTRY* lpEntries;

};

再看CmainFrame中的程式碼:

static const AFX_MSGMAP messageMap =    { &TheBaseClass::GetThisMessageMap, _messageEntries[0] };

就知道:這個表格其實是一個連結串列,放的是基類的GetMassageMap函式指標,和當前類的真正表格_messageEntries的入口地址。再看_messageEntries裡面放的又是什麼:

nMessage

nCode

nID

nLastID

nSig

nPfn

WM_CREATE

0

0

0

AfxSig_is

&CmainClass::OnCreate

0

0

0

0

AfxSig_end

0

好了,現在看到了WM_CREATE 就連著 &CmainClass::OnCreate,再結合上面CWnd::OnWndMsg的程式碼:

    for (; pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)

        if (message < 0xC000)

            if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)

                goto LDispatch;

就可以推理得到,AfxFindMessageEntry就是在找message和_messageEntries.nMessage的相等,如果不等,就往基類裡面找: pMessageMap = pMessageMap->pBaseMap

找到了就跳到Ldispatch,其中,有:mmf.pfn = lpEntry->nPfn;這個就是放著該訊息處理函式的內容。

3、   總結:

其實那些巨集的作用就是用來初始化那個連結串列messageMap 和當前類處理函式的表格_messageEntries,當框架程式碼執行到pMessageMap = GetMessageMap();時候,由於虛擬函式的作用,使得當面運行當前視窗CmainFrame的GetMassageMap,把CmainFrame的表格給pMessageMap,然後再從派生類往基類一層層找響應的處理函式。

這與c++虛擬函式機制相比,其好處是可以省空間,每個類裡面只有自己重寫的函式資訊,其他資訊在基類裡面找;
不好的地方就是搜連結串列,從當前類往基類上搜,時間上可能久一點;

mfc訊息分派的基本原理就是這樣的,但是還有很多的細節沒有深入,比如那些不同的成員函式有不同的引數,他們是怎麼傳遞的,怎麼在表格裡面統一引數的資訊...這些看下面的參考文獻【1】,有較為詳細的說明。

4、   補充:用的框架程式碼裡面,最顯著的是一種模式:模板方法模式(template method pattern)。具體

的說明請參考其他文章。

這裡為了補充,舉一個例子說明是怎麼呼叫的:

class A
{
public:
    void callPrint(){ print(); };
protected:
    static void printThis(){ cout << "this is a A object!/n"; };
private:
    virtual void print(){ printThis(); };

};

class B : public A
{
protected:
    static void printThis(){ cout << "This is a B object!/n";}
private:
    void print(){ printThis();}
};

如下語句輸出什麼:

B  b;

A *pa = &b;

pa->callPrint();

也許你知道輸出的是“this is a B object!/n”,但是,知道為什麼嘛?參考文章【3】中有詳細的講解,會令你滿意的。

(注意上面例子中的virtual和訪問許可權,試著利用虛擬函式的實現機制來解釋他是怎麼作用的。)

參考文章: