MFC消息处理函数是怎么映射的

本文探讨了MFC如何通过宏建立消息映射,将消息处理函数与ID关联。通过分析类头文件中的宏定义和消息映射结构,揭示了函数指针如何指向类的成员函数,尤其是不同参数类型的成员函数。示例代码展示了类的静态成员变量初始化和联合体用于调用成员函数的过程。最后提出了关于这种映射方式是否仅为了节省虚函数开销的疑问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 用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来对应的,但又用这样的网来建立,难道仅仅是为了省略虚函数的消耗吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值