之前被问过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的很多操作可以在向导中选择,会自动添加标准代码。
MFC消息映射BEGIN_MESSAGE_MAP详解
最开始debug,调用堆栈没有显示到wincore.cpp中去,我看了这个文章,知道wincore.cpp是可以debug进去的。展开调用堆栈的dll,然后选了个加载Microsoft符号库,就显示了wincore.cpp中的调用堆栈。