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
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成