[MFC] 消息映射机制的使用和原理浅析

之前被问过MFC消息机制的问题,这里记录一下。
由于水平有限,可能不是很深入,以后如果有机会,继续研究。

如何使用消息映射

消息映射是在以下代码中的BEGIN_MESSAGE_MAP-END_MESSAGE_MAP部分完成的:

BEGIN_MESSAGE_MAP(CMFCMessagetestDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()

例如新建一个MFC对话框项目。默认生成了一个窗口。
在这里插入图片描述
MFC-Message-testAPP: App类,是程序的入口
MFC-Message-testDlg:窗口类,负责窗口部分的代码
MFC-Message-testAPP中模态化调用了MFC-Message-testDlg:

CMFCMessagetestApp::InitInstance():
	CMFCMessagetestDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();

在MFC-Message-testDlg的资源视图中添加一个button控件:
在这里插入图片描述
可以在button属性中,看到button的默认ID:
在这里插入图片描述
点击属性窗口的事件图标,为button添加click事件:
在这里插入图片描述
此时CMFCApplication1Dlg中自动生成了两段代码:

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
END_MESSAGE_MAP()
void CMFCApplication1Dlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
}

把TODO部分改为一个AfxMessageBox,就可以实现点击按钮,弹窗的效果。

void CMFCApplication1Dlg::OnBnClickedButton1()
{
	AfxMessageBox(L"Button clicked!");
}

在这里插入图片描述

如何自定义消息

在类向导中,添加自定义消息和响应函数:
在这里插入图片描述
自动生成了以下代码:

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
	ON_MESSAGE(MY_MSG_1, &CMFCApplication1Dlg::OnMyMsg1)
END_MESSAGE_MAP()
afx_msg LRESULT CMFCApplication1Dlg::OnMyMsg1(WPARAM wParam, LPARAM lParam)
{
	return 0;
}

此时编译会报错,因为MY_MSG_1这个宏还没定义。添加以下宏定义:

#define MY_MSG_1 (WM_USER + 1)

把两个响应函数修改一下:

void CMFCApplication1Dlg::OnBnClickedButton1()
{
	//点击按钮的时候,将MY_MSG_1消息,发送到本窗口
	::SendMessage(this->GetSafeHwnd(),MY_MSG_1,NULL,NULL);	
}

afx_msg LRESULT CMFCApplication1Dlg::OnMyMsg1(WPARAM wParam, LPARAM lParam)
{
	//OnMyMsg1消息的响应函数,弹出一个消息框
	AfxMessageBox(L"OnMyMsg1rcvd!");
	return 0;
}

此时,还是可以实现点击按钮弹出消息框的操作:
在这里插入图片描述

消息映射的原理

可以看到消息映射无论是添加,还是读代码,都挺方便。消息映射的核心就是:MESSAGE_MAP

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
	ON_MESSAGE(MY_MSG_1, &CMFCApplication1Dlg::OnMyMsg1)
END_MESSAGE_MAP()

AFX_MSGMAP是什么

在CMFCApplication1Dlg的头文件中,可以看到声明了DECLARE_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()宏的源码为:

#define DECLARE_MESSAGE_MAP() \
protected: \
	static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

所以类声明中,添加了DECLARE_MESSAGE_MAP()是引入了两个函数声明。

BEGIN_MESSAGE_MAP的宏定义如下:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
	PTM_WARNING_DISABLE \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return GetThisMessageMap(); } \
	const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
	{ \
		typedef theClass ThisClass;						   \
		typedef baseClass TheBaseClass;					   \
		__pragma(warning(push))							   \
		__pragma(warning(disable: 4640)) /* message maps can only be called by single threaded message pump */ \
		static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
		{

END_MESSAGE_MAP的宏定义如下:

#define END_MESSAGE_MAP() \
		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
	}; \
		__pragma(warning(pop))	\
		static const AFX_MSGMAP messageMap = \
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
		return &messageMap; \
	}								  \
	PTM_WARNING_RESTORE

可以看到:
BEGIN_MESSAGE_MAP中是GetThisMessageMap函数体的开头;
END_MESSAGE_MAP中是GetThisMessageMap函数体的结尾。
GetThisMessageMap函数中,定义了一个static const AFX_MSGMAP_ENTRY _messageEntries[]数组,数组的元素就是以下这些东西。

	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
	ON_MESSAGE(MY_MSG_1, &CMFCApplication1Dlg::OnMyMsg1)

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)
};

ON_MESSAGE宏的定义如下:

define ON_MESSAGE(message, memberFxn) \
	{ message, 0, 0, 0, AfxSig_lwl, \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
		(memberFxn)) },

所以ON_MESSAGE(MY_MSG_1, &CMFCApplication1Dlg::OnMyMsg1)就是为AFX_MSGMAP_ENTRY数组初始化一个这样的成员:

{
	UINT nMessage == MY_MSG_1
	UINT nCode == 0
	UINT nID == 0
	UINT nLastID == 0
	UINT_PTR nSig == AfxSig_lwl
	AFX_PMSG pfn == &CMFCApplication1Dlg::OnMyMsg1
};

GetThisMessageMap函数首先构建了一个AFX_MSGMAP_ENTRY的数组。
然后,返回了以下static const AFX_MSGMAP messageMap对象的指针:

static const AFX_MSGMAP messageMap = \
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
		return &messageMap;

AFX_MSGMAP 定义如下:

struct AFX_MSGMAP
{
	const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
	const AFX_MSGMAP_ENTRY* lpEntries;
};

static const AFX_MSGMAP messageMap 包含两个成员,第一个是指向父类的static messageMap的指针,第二部分是自己的AFX_MSGMAP_ENTRY数组的指针。

所以BEGIN_MESSAGE_MAP到END_MESSAGE_MAP部分的代码,就是在构建一个AFX_MSGMAP对象。

当消息来临时,MFC就是通过这个AFX_MSGMAP中的AFX_MSGMAP_ENTRY数组,根据消息ID,找到对应的函数指针。

MFC什么时候访问AFX_MSGMAP的

这需要调试一下,在OnMyMsg1函数中打一个断点,然后当运行到这个函数的时候,看看调用堆栈,经过了哪些函数。
如图,函数堆栈调用如下:
在这里插入图片描述
一般的消息,都是AfxWndProcBase()->AfxWndProc->AfxCallWndProc()-> WindowProc()->OnWndMsg()这样的消息处理过程。

其中,AfxWndProcBase()->AfxWndProc->AfxCallWndProc(),这3个是MFC的消息处理函数,他们会分辨是哪个窗口的消息,并获取窗口的句柄。
然后调用窗口的WindowProc(),WindowProc()是CWnd类中的虚函数,如果CMFCApplication1Dlg重写了WindowProc(),按就会先调用CMFCApplication1Dlg的WindowProc(),再由它来调用基类的WindowProc(),最终调用到CWnd的WindowProc()。

CWnd的WindowProc()代码如下:

//wincore.cpp
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	// OnWndMsg does most of the work, except for DefWindowProc call
	LRESULT lResult = 0;
	if (!OnWndMsg(message, wParam, lParam, &lResult))
		lResult = DefWindowProc(message, wParam, lParam);
	return lResult;
}

它会调用OnWndMsg函数,就在这个函数中,处理的AFX_MSGMAP:

//wincore.cpp
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
......
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
{
		......
}
else
{
		// not in cache, look for it
		pMsgCache->nMsg = message;
		pMsgCache->pMessageMap = pMessageMap;
		......
}
......
}

上面在讲DECLARE_MESSAGE_MAP时,提到了GetMessageMap的函数声明:
virtual const AFX_MSGMAP* GetMessageMap() const;
它是个虚函数,所以当执行的是CMFCApplication1Dlg的处理时,调用的是CMFCApplication1Dlg的GetMessageMap,获取的就是CMFCApplication1Dlg的{消息ID,回调函数指针} 的映射数组。
因此可以在OnWndMsg中,调用MY_MSG_1的映射函数OnMyMsg1()。

结论

以上就是关于MFC消息映射机制的总结。

还有些内容需要总结,比如MFC消息的分类、一些重点消息的使用、
为什么消息都是由AfxWndProc处理的,等等。
有时间再来记录。

参考

C语言/C++教程 大型源码案例分析:MFC消息系统的代码解析 易道云编程
如果对MFC消息没什么了解,可以看一下这个视频,我觉得讲的挺好的。
可以开1.5倍速。

VS2013/MFC 利用类向导添加自定义消息
MFC的很多操作可以在向导中选择,会自动添加标准代码。

sendMessage 函数 (winuser.h)

MFC消息映射BEGIN_MESSAGE_MAP详解
最开始debug,调用堆栈没有显示到wincore.cpp中去,我看了这个文章,知道wincore.cpp是可以debug进去的。展开调用堆栈的dll,然后选了个加载Microsoft符号库,就显示了wincore.cpp中的调用堆栈。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值