目录
消息映射机制概述
宏展开
宏展开的作用
消息映射机制的执行流程
消息处理
消息映射机制概述
MFC的消息映射映射机制是可以在不重写WindowProc虚函数的大前提下,仍然可以处理消息。
类必须具备的要件
类内必须添加声明宏 DECLARE_MESSAGE_MAP()
类外必须添加实现宏:
- BEGIN_MESSAGE_MAP(theClass , baseClass)
- END_MESSAGE_MAP()
总结:当一个类具备上述两个要件,这个类就可以按照消息映射机制来处理消息。
MFC利用消息映射机制处理消息:以处理WM_CREATE消息为例
类内:
- 添加声明宏 DECLARE_MESSAGE_MAP()
- 添加处理WM_CREATE消息的函数声明
类外:
- 添加宏:BEGIN_MESSAGE_MAP(theClass , baseClass) END_MESSAGE_MAP()
- 实现处理WM_CREATE消息的函数定义
宏展开
对宏代码进行展开,得到下面的成果
#include <afxwin.h>
class CMyFrameWnd : public CFrameWnd {
// DECLARE_MESSAGE_MAP()
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
public:
LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
};
//BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
// ON_MESSAGE( WM_CREATE, OnCreate )
//END_MESSAGE_MAP()
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
return &messageMap;
}
LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam) {
AfxMessageBox("WM_CREATE");
return 0;
}
class CMyWinApp : public CWinApp {
public:
virtual BOOL InitInstance();
};
CMyWinApp theApp;//爆破点
BOOL CMyWinApp::InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, "MFCCreate");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
声明了两个成员函数
DECLARE_MESSAGE_MAP()
声明两个函数:
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
拓展:在C++中,static修饰函数可以有以下两种含义:
(1) 静态成员函数,特点包括:
- 不属于类的任何特定对象,而是属于整个类。
- 可以访问类的静态成员变量和其他静态成员函数,但不能直接访问类的非静态成员变量和非静态成员函数。
- 不能使用this指针,因为this指针指向类的对象实例,而静态成员函数并不属于任何特定对象。
- 静态成员函数可以直接通过作用域解析运算符(::)访问类的静态成员变量和静态成员函数,无需通过对象。
静态成员函数通常用于执行与类相关的操作,而不依赖于特定对象的状态。例如,可以在静态成员函数中计算或处理类的静态成员变量,或者实现与类相关的全局操作。
(2) 文件作用域的静态函数
文件作用域的静态函数是指在C或C++中使用static关键字声明的函数,这种函数的作用域限定在当前文件内,不能被其他文件访问或调用。在文件中使用static修饰的函数通常用于实现模块内部的辅助函数或者限制函数的作用域,以减少全局命名空间的污染。
拓展:在C++中,const修饰函数可以分为两种情况:const成员函数和const修饰的非成员函数。
(1) const成员函数:const成员函数是指在函数声明或定义的末尾加上const关键字,用于表示该成员函数不会修改对象的状态。在const成员函数中,不能修改成员变量的值,也不能调用非const成员函数,以确保该函数不会改变对象的状态。
(2) const修饰的非成员函数: const修饰的非成员函数是指在函数声明或定义的末尾加上const关键字,用于表示函数的返回值是常量。
示例:
const int getValue() {
return 10; // 返回一个常量值
}const string& getName() {
static const string name = "John";
return name; // 返回一个常量引用
}
实现了两个函数
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
END_MESSAGE_MAP()
第一个函数的返回值是第二个函数的返回值
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
return &messageMap;
}
ON_MESSAGE( WM_CREATE, OnCreate ) 相当于
{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },
宏展开的作用
首先学习两个数据结构
这个结构体,与我们需要处理的消息有关,主要需要关注第一个与最后一个即可
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;
UINT nCode;
UINT nID;
UINT nLastID;
UINT_PTR nSig;
AFX_PMSG pfn;
};
这个结构体的成员表示如下:
- 消息ID,用于标识Windows消息类型的整数值。
- 通知码,标识控件ID值
- 命令ID,用于区分控件的不同命令,比如:有一个“打开”菜单项和一个“保存”菜单项,它们分别对应着打开文件和保存文件的操作。在程序内部,为了识别用户点击了哪个菜单项,就需要为每个菜单项分配一个唯一的命令ID。当用户点击“打开”菜单项时,程序就会根据这个命令ID来执行打开文件的操作;当用户点击“保存”菜单项时,程序则会根据另一个命令ID来执行保存文件的操作。
- 最后一个命令ID,用于标识此消息关联的前一个命令的整数值。
- 处理消息的函数类型
- 处理消息的函数名(地址)
这个结构体主要和遍历链表有关
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
这个结构体的成员表示如下:
- 父类宏展开的静态变量地址
- 本类宏展开的静态数组首地址
宏展开各部分的作用
局部静态变量:与普通的局部变量不同之处在于其生存期和作用域。当函数被调用时,静态局部变量不会被销毁,而是保留其数值,直到程序运行结束。此外,在函数内部,静态局部变量的作用域仅限于声明它的函数内部。
GetThisMessageMap():静态函数
作用:定义静态变量和静态数组,并返回本类静态变量地址(获取链表头)
_messageEntries[]:静态数组(进程级声明周期)
作用:数组每个元素,保存为 消息ID 和 处理消息的函数名(地址)
messageMap:静态变量(进程级声明周期)
作用:第一个成员,保存父类宏展开的静态变量地址(负责连接链表)
第二个成员,保存本类的静态数组首地址
GetMessageMap():虚函数
作用:返回本类静态变量地址(获取链表头)
两个结构体在消息映射机制实现的作用
- messageMap第一个是父类GetThisMessageMap函数地址,第二个是本类的_messageEntries数组地址
- _messageEntries结构体数组地址中的每一个元素都是消息和对应处理函数地址
CMyFrameWnd有一套这样的局部静态本类,CFrameWnd,CWnd都有,到此为止,CWnd的父类就没有了
这就构成一个链表的结构:
在 CFrameWnd,CWnd 类中都有对消息的处理函数
消息映射机制的执行流程
下载WM_CREATE消息处理函数下断点
消息产生进入窗口处理函数(AfxWndProc),对此函数下断点开始分析,前三个消息不是WM_CREATE消息,先F5放过,直到 nMsg 值为1
通过句柄拿到框架窗口对象
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
之后调用AfxCallWndProc,在之后调用WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
再之后调用OnWndMsg
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
在这里对不同的消息处理都不一样
获取本类宏站开的静态变量的地址(链表头结点)
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
F11,回到我们的代码了
开始遍历结构体数组,循环中每次迭代条件就是获得父类GetThisMessageMap函数地址
如果找到返回找到的数组元素的地址,如果没找到返回NULL,找到之后goto跳出循环
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatch;
}
lpEntry->pfn; //CMyFrameWnd::OnCreate
调用CMyFrameWnd::OnCreate函数完成消息的处理
LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam) {
AfxMessageBox("WM_CREATE");
return 0;
}
执行完后,再次会到这里
总结:
- 消息产生进入窗口处理函数(AfxWndProc )
- 根据已知窗口句柄,找到和它绑定在一起的框架类对象地址( pFrame )。
- 利用框架类对象地址( pFrame )调用框架类成员虚函数WindowProc
- 获取本类对应的静态变量,并到对应数组中匹配查找。
- 如果没有找到获取父类对应的静态变量,并到对应数组中匹配查找。
- 如果找到了,利用找到的数组元素的最后一个成员,并调用之,完成消息处理。
消息分类
主要有以下三类:
- 标准windows消息:ON_WM_XXX
- 自定义消息:ON_MESSAGE
- 命令消息:ON_COMMAND,暂且不管
第一类消息处理函数的返回值,参数都是固定的,第二类就不是了。这些规定可以再MSDN中查到