MFC消息映射的原理:筆記


多態的實現機制有兩種,一是通過查找絕對位置表,二是查找名稱表;兩者各有優缺點,那么為什么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和訪問權限,試着利用虛函數的實現機制來解釋他是怎么作用的。)

 

 

參考文章:

 

【1】、MFC消息分派:http://blog.csdn.net/linzhengqun/archive/2007/11/28/1905671.aspx

 

【2】、MFC教程之消息映射的實現:http://www.vczx.com/tutorial/mfc/mfc4.php

 

【3】、與大蝦對話:領悟設計模式:http://www.myfaq.com.cn/A200508/2005-08-07/183608.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM