用MFC的ClassWizard创建的工程,MFC利用几个宏建立了一个消息映射网,把一个消息处理函数和一个ID捆绑起来。至于消息映射网的建立,在侯先生的<深入浅出MFC>中已经解释的很清楚了,在此不说。在看书的时候有一个问题当时没有想明白,它是怎么把一个类的成员函数赋值给一个函数指针的?还有对于带有不同参数类型的成员函数,它是怎么用一个函数指针给统一赋值的?
现在看一下在类头文件中用到一个宏是怎么定义的(AFXWIN.H)
#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; /
其中重要的一个AFX_MSGMAP_ENTRY的定义是(AFXWIN.H)
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 nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
pfn是一个函数指针,它的定义为
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
再看一下在类的CPP文件中:
BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//{{AFX_MSG_MAP(CTestDlg)
ON_WM_PAINT()
ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
它们被解释为:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) /
const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() /
{ return &baseClass::messageMap; } /
const AFX_MSGMAP* theClass::GetMessageMap() const /
{ return &theClass::messageMap; } /
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = /
{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; /
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = /
{ /
#define ON_WM_PAINT() /
{ WM_PAINT, 0, 0, 0, AfxSig_vv, /
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint },
#define ON_BN_CLICKED(id, memberFxn) /
ON_CONTROL(BN_CLICKED, id, memberFxn)
#define ON_CONTROL(wNotifyCode, id, memberFxn) /
{ WM_COMMAND, (WORD)wNotifyCode, (WORD)id, (WORD)id, AfxSig_vv, /
(AFX_PMSG)&memberFxn },
#define END_MESSAGE_MAP() /
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /
}; /
从以上消息映射网的建立过程可以看出,它首先建立了一个类的静态成员_messageEntries[],
并在类的外面给它初始化赋值,在此给其函数指针赋值的,如果你定义的消息映射函数不是void类型的,它就直接强制转换成这个类型的。在这里直接用了强制转换。
那么它调用的时候是怎么转换这个函数指针的呢,原来,它们利用了一个联合的结构来初始化这个函数指针的,下面我用了一个类来简单的模拟这个过程。
#include "stdafx.h"
class CF; //类的声明
typedef void(CF::* PFUNC)(); //函数指针类型的定义
struct AAA //相当于AFX_MSGMAP_ENTRY
{
PFUNC pfn; //该结构只有一个函数指针
};
class CF
{
public:
CF(){}
void Func(){printf("in Func./n");}
void FuncA(int i){printf("in FuncA.==%d/n",i);} //定义了两个函数
static AAA entry[]; //定义静态的成员变量_messageEntries[];
};
//给类的成员变量初始化,就是给函数指针赋值,实际上在这儿赋的应该是类的成员函数的偏移地址
AAA CF::entry[] =
{ CF::Func,(PFUNC)(void(CF::*)(int i))FuncA};
//定义联合,用来格式化内存块
union MMF
{
PFUNC pfn;
void(CF::* pfn_Func)(); //有那两个函数的类型声明
void(CF::* pfn_FuncA)(int i);
};
int main(int argc, char* argv[])
{
//printf("Hello World!/n");
PFUNC pfn; //定义一个函数指针
pfn = CF::entry[1].pfn; //取得类中一个成员函数的地址,但此时还不能直接调用,否 //则编译都通不过。
MMF m; //定义联合体
m.pfn = pfn; //赋以函数地址
CF f; //定义类的实例,这是必须的,否则只能使用静态成员函数了。
(f.*m.pfn_FuncA)(3); //仔细看一下这儿的调用方式,有点怪异啊
//联合体m和类的实例f在这儿都是局部变量,应该没什么关系的,但用f.去调用m的成员,是不是有点怪呢//?
//实际上,m.pfn_FuncA在这儿内存中就是函数CF::FuncA的偏移地址,是函数指针,所以它前面要加星号。
//这样调用和用f.FuncA是一样的。
return 0;
}
另:在实际的消息映射中,它还是记录了每个函数的一个ID来对应的,但又用这样的网来建立,难道仅仅是为了省略虚函数的消耗吗?