[原创]如何在MFC中定位按钮事件
1,MFC消息映射机制
MFC消息映射机制有关的宏有下面几个:
DECLARE_MESSAGE_MAP()宏
BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
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; /
virtual const AFX_MSGMAP* GetMessageMap() const; /
从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
定义一个长度不定的静态数组变量_messageEntries[];
定义一个静态变量messageMap;
定义一个虚拟函数GetMessageMap();
在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
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 nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
(2)AFX_MSGMAP的定义
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。
BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
它们的定义如下:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) /
const AFX_MSGMAP* theClass::GetMessageMap() const /
{ return &theClass::messageMap; } /
//DECLARE_MESSAGE_MAP()中有一个messageMap
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = /
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; /
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = /
{ /
#define END_MESSAGE_MAP() /
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /
}; /
对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
const AFX_MSGMAP* CView::GetMessageMap() const
{
return &CView::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
{
&CWnd::messageMap, 父类的MessageMap
&CView::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。
到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
//第二个参数为函数地址 消息映射
#define ON_COMMAND(id, memberFxn) /
绑定消息与函数 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
如下:
{WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
(AFX_PMSG)&OnFileNew},
BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP() 宏中间的宏的作用就是填充 _messageEntries数组。

2,原理
GetMessageMap是虚函数,存在于虚函数表里,只要找到全局应用程序对象指针就可以了。
下图为一个MFC程序的入口,程序会先进入_tWinMain,然后再进入AfxWinMain。

继续跟进可以看到调用AfxGetApp就可以得到应用程序指针。

3,OD分析
用VC6.0建立一个如图的mfc程序,并设置2个按钮事件,和相应的处的事件处理函数。编译后在拖入OD中。


找到getMoudleHandleA函数的后的下一个call就是_twinmain函数,F7进入函数内部。

进入函数后,里面只有一个call,这个函数就是afxWinMain,再F7进入,

找到afxGetApp函数,这个函数会返回应用程序指针,返回值放入到eax中,执行到0048c11a位置。

点击eax的值在数据窗口中跟随,里面存储了005b4064这个就是虚函数表的指针。


点击005b4064,在数据窗口跟随,就找到了应用程序的虚函数表。到底哪一个才是虚函数GetMessageMap()呢?以下就是这个虚函数的实现,通过查看反汇编
可以发现这个虚函里面只有一个功能实现,mov eax,变量 这个就是它的特征。
然后在虚函表里面去查找有这种特征的函数,00401037所在的函数存在这种特种,然后反汇编跟随。
const AFX_MSGMAP* theClass::GetMessageMap() const /
{ return &theClass::messageMap; }


下面选中行就是 虚函数GetMessageMap,但是我们当前找到的是应用程序的 GetMessageMap虚函数,但是我们要找的是窗口的虚函数。
经过测试发现,窗口的虚函数也在下面这篇区域。


在窗口的 GetMessageMap 虚函数特征的下一句下断点,执行到这里获得eax的值,点击eax的值在数据窗口跟随。然后找到的是
messageMap,而它的定义如下。
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap; 父类的
const AFX_MSGMAP_ENTRY* lpEntries; 自己的
};
所以第二个值005b410才是_messageEntries数组,我们点击第二个值,在数据窗口中跟随。



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)
};
然后依次查看每个消息的执行函数,可以发现倒数2个是我们的按钮事件。


注意:上传附件及图片大小不得大于30M。
⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。
