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和訪問許可權,試著利用虛擬函式的實現機制來解釋他是怎麼作用的。)
參考文章: