MFC的消息映射对于对开发者处理消息可谓十分方便。MFC类继承众多,虚函数表占内存大导致微软直接不采用虚函数方式。发现《VC++深入详解》只大概说了消息映射的原理,没有详细介绍其实现,所以写篇小文章探究下。
一、首先在使用消息映射之前,必须先声明DECLARE_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()是个宏定义,对应的源码为:
#define DECLARE_MESSAGE_MAP()
private:
static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
static AFX_DATA const AFX_MSGMAP messageMap;
static const AFX_MSGMAP* PASCAL _GetBaseMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
声明添加了两个成员变量和两个成员函数:
_messageEntries: 是一个AFX_MSGMAP_ENTRY(定义了消息路由)类型数组,即路由表。(消息传递路径)
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; //消息类型
UINT nCode; // 控制码
UINT nID; // 控件ID
UINT nLastID; // 控件ID范围, 对于单控件消息处理,与nID相同
UINT nSig; // 信号类型
AFX_PMSG pfn; //回调函数,即处理函数(PMSG即Process Message)
};
messageMap: 路由信息,包含父类路由信息指针,和本类的路由表指针。
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap; //指向父类的指针
const AFX_MSGMAP_ENTRY* lpEntries; //本类路由表指针
};
二、接着我们查看BEGIN_MESSAGE_MAP在.cpp文件中的定义
BEGIN_MESSAGE_MAP(CMfc_testApp, CWinApp)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
END_MESSAGE_MAP()
可以看到BEGIN_MESSAGE_MAP也是一个宏。然后上面的ON_COMMAND也是宏定义,全部展开后代码为:
const AFX_MSGMAP* theClass::GetMessageMap() const
{
return &theClass::messageMap;
}
const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap()
{
return &baseClass::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =
{
&baseClass::messageMap, //基类路由信息指针
&theClass::_messageEntries[0] //本类路由表数组地址
};
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
{
{WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
可以看到,通过宏定义和消息内嵌的方式,已经全部初始化消息路由相关的成员变量和方法,结构如下图所示:
每个消息映射入口的格式如下:
ON_Notification(id, memberFxn)
其中id指定了发送通知的控件的子窗口的ID,而memberFxn指定了处理该通知的父对象中的成员函数名。
父对象的 函数原型格式如下:
afx_msg void memberFxn( );
可能的消息映射入口如下:
映射入口
何时向父对象发送消息
ON_BN_CLICKED
用户单击按钮时
ON_BN_DOUBLECLICKED
用户双击按钮时
三、然后消息循环:CWnd::OnWndMsg(位于WINCORE.cpp文件中)
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
{
//处理在当前类的路由表和缓存命中
}
else
{
// 当前类路由表和缓存找不到,
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;
//通过pMessageMap = pMessageMap->pBaseMap递归往基类深入查找匹配
for (; pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
{
.....
}
.....
}
系统为每个窗体或控件都提供了默认的消息处理函数,如果在当前类中重写了消息处理函数,则采用当前消息处理函数,如果没有重写,则调用默认消息处理函数,最终保证消息得到处理。