消息系统是MFC的重要组成部分。MFC的消息响应机制并不复杂,而且MFC的开发环境 Visual Studio来供了非常好的自动化工具,自动生成代码。MFC也定义了丰富的宏来简化消息响应的代码。这使得很多初学者都能快速开发出基于消息响应机制的应用程序。然后也正是这些带来方便的宏,使很多人摸不着头脑。当不小心的代码操作使自动化工具不好用的时候,看着那一组更像是一堆的宏,很多人只好一头雾水。
要想真正了解MFC的消息机制,必需弄清楚这些宏。好在源码面前无秘密,我们将从这些宏的源码着手,逐步分析、了解、并学习MFC的消息响应及映射机制。
第一个宏:DECLARE_MESSAGE_MAP()
作用:为一个消息响应类声明必需的成员变量和成员函数。
我们在窗口类、应用程序类、文档类、视图类、以及这些类的子类的定义中,都能看到DECLARE_MESSAGE_MAP()宏,通常被自动化工具声明在类的最后部分,如:
// 生成的消息映射函数
protected :
DECLARE_MESSAGE_MAP()
};
DECLARE_MESSAGE_MAP()
protected :
DECLARE_MESSAGE_MAP()
};
DECLARE_MESSAGE_MAP()
宏定义如下(在DLL类型和WINDOWS程序类型下,定义会有不同,本文只分析非DLL类型,下同):
#define DECLARE_MESSAGE_MAP()
private :
static const AFX_MSGMAP_ENTRY _messageEntries[];
protected :
static const AFX_MSGMAP messageMap;
virtual const AFX_MSGMAP* GetMessageMap() const ;
private :
static const AFX_MSGMAP_ENTRY _messageEntries[];
protected :
static const AFX_MSGMAP messageMap;
virtual const AFX_MSGMAP* GetMessageMap() const ;
可以看到,宏DECLARE_MESSAGE_MAP()定义了两个静态成员变量,并重载了一个虚函数。下面分析一下这三个成员:
_messageEntries被定义为一个AFX_MSGMAP_ENTRY类型的数组。结构体AFX_MSGMAP_ENTRY的定义如下:
struct
AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
通过对消息响应过程的源码分析可知,nSig事实上是一系列编码,每一种编码代表一种响应函数的类型,包括返回值、参数信息等。在响应消息的时候,将把pfn指向的函数指针强制类型转换为nSig代表的函数类型,然后再调用。
pfn的类型AFX_PMSG定义如下,意为CCmdTarget的成员函数:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
由此我们可以得出:静态成员_messageEntries为是一个消息到函数的映射表,或叫消息入口表。通过查找此表,可以找到消息的响应函数。
DECLARE_MESSAGE_MAP()宏声明的另一个静态成员变量messageMap被定义为AFX_MSGMAP类型。AFX_MSGMAP定义如下:
struct
AFX_MSGMAP
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};
{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};
struct
AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
至于DECLARE_MESSAGE_MAP()宏重载的虚函数GetMessageMap,可以猜测只是用来返回成员messageMap的地址而已。因为GetMessageMap是虚函数,所以系统只要通过调用消息响应类的基类CCmdTarget类的GetMessageMap函数,便可以找到最后一级子类的消息映射信息。我们将在接下来对其它几个宏的分析中得到相同的结论。
第二个重要的宏:BEGIN_MESSAGE_MAP
作用:定义DECLARE_MESSAGE_MAP宏声明的静态变量。
BEGIN_MESSAGE_MAP定义的源代码如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass)
const AFX_MSGMAP* theClass::GetMessageMap() const
{ return & theClass::messageMap; }
AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
{ &baseClass::messageMap, &theClass::_messageEntries[0 ] };
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
{
const AFX_MSGMAP* theClass::GetMessageMap() const
{ return & theClass::messageMap; }
AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
{ &baseClass::messageMap, &theClass::_messageEntries[0 ] };
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
{
BEGIN_MESSAGE_MAP宏首先定义了函数GetMessageMap的函数体,如前文所述,直接返回当前类的成员变量messageMap的地址。
const AFX_MSGMAP* theClass::GetMessageMap() const
{ return &theClass::messageMap; }
{ return &theClass::messageMap; }
AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
{ &baseClass::messageMap, &theClass::_messageEntries[0 ] };
{ &baseClass::messageMap, &theClass::_messageEntries[0 ] };
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
{
{
第三个宏END_MESSAGE_MAP()
作用:定义_messageEntries数组初始化代码的结束部分。
#define END_MESSAGE_MAP()
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
#define ON_COMMAND(id, memberFxn)
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v,
static_cast <AFX_PMSG> (memberFxn) },
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v,
static_cast <AFX_PMSG> (memberFxn) },
通过以上分析,我们可以得到一个链表式的数据结构,子类的messageMap成员为链表的头节点。链表的每个节点都包含一个消息入口表。MFC的消息系统的标准备消息处理函数CCmdTarget::OnCmdMsg正是通过这样一个链表查找到消息的响应函数,并调用该函数来响应消息。