之前写了[MFC] 消息映射机制的使用和原理浅析,还有些需要补充的,都记在这里。
MFC 消息的分类
MFC消息分为系统消息和自定义消息。
图片来源:C语言/C++教程 大型源码案例分析:MFC消息系统的代码解析 易道云编程
系统消息分为窗口消息、命令消息、通知消息。消息ID是0 ~ 1023。
每个窗口的自定义消息的消息ID需要从1024开始。声明方法为:
#define MY_MSG_1 (WM_USER + 1)
SendMessage
[MFC] 消息映射机制的使用和原理浅析中,用SendMessage发了一个消息。
void CMFCApplication1Dlg::OnBnClickedButton1()
{
::SendMessage(this->GetSafeHwnd(),MY_MSG_1,NULL,NULL);
}
SendMessage发出的消息是不经过消息循环的。
所以debug时可以看到,没有经过任何loop函数,直接就是AfxWndProc了,最终通过MessageMap找到对应的响应函数。
此时的函数堆栈调用:
未排队的消息
未排队的消息会立即发送到目标窗口过程,绕过系统消息队列和线程消息队列。 系统通常发送未排队的消息,以通知窗口影响它的事件。 例如,当用户激活新的应用程序窗口时,系统会向窗口发送一系列消息,包括 WM_ACTIVATE、 WM_SETFOCUS和 WM_SETCURSOR。 这些消息通知窗口已激活,键盘输入已定向到窗口,鼠标光标已在窗口边框内移动。 当应用程序调用某些系统函数时,也可能导致未排队的消息。 例如,在应用程序使用 SetWindowPos 函数移动窗口后,系统会发送WM_WINDOWPOSCHANGED消息。
发送非排队消息的一些函数包括 BroadcastSystemMessage、 BroadcastSystemMessageEx、 SendMessage、 SendMessageTimeout 和 SendNotifyMessage。
来源:Microsoft Learn 关于消息和消息队列
PostMessage 和 消息循环
把上面的SendMessage改成PostMessage试一下。
void CMFCApplication1Dlg::OnBnClickedButton1()
{
::PostMessage(this->GetSafeHwnd(),MY_MSG_1,NULL,NULL);
}
此时的函数堆栈调用:
可以看到调用堆栈不同。
在进入AfxWndProc之前,进入了RunModalLoop。
RunModalLoop就是CWnd的消息循环处理函数。
所以用SendMessage发出的消息,没有经过消息队列。
使用PostMessage发出的消息,会进入系统的消息队列,需要窗口的消息循环来处理。
线程可以使用 PostMessage 或 PostThreadMessage 函数将消息发布到其自己的消息队列或另一个线程的队列。
来源:Microsoft Learn 关于消息和消息队列
消息循环中,主要做了以下事情:
第一个循环:
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
......
PeekMessage 消息,获取消息,而不在队列中删除消息。这里对PeekMessage的返回值进行了非判断,当取不到消息的时候,进行IDLE空闲判断和处理。
第二个循环:
// phase2: pump messages while available
do
{
......
}while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
这个循环也是PeekMessage获取消息,然后在循环体中,进行pump meessage(消息泵)
// pump message, but quit on WM_QUIT
if (!AfxPumpMessage())
AfxPumpMessage中,会GetMessage()、AfxPreTranslateMessage()、TranslateMessage()、DispatchMessage()。
系统为每个 GUI 线程维护一个系统消息队列和一个特定于线程的消息队列。
~
每当用户移动鼠标、单击鼠标按钮或键盘上的类型时,鼠标或键盘的设备驱动程序会将输入转换为消息,并将其置于系统消息队列中。 系统一次从系统消息队列中删除一个消息,检查它们以确定目标窗口,然后将其发布到创建目标窗口的线程的消息队列。 线程的消息队列接收线程创建的窗口的所有鼠标和键盘消息。 线程从其队列中删除消息,并指示系统将其发送到相应的窗口过程进行处理。
来源:Microsoft Learn 关于消息和消息队列
PreTranslateMessage和WindowProc
这里提一下PreTranslateMessage函数。
PreTranslateMessage是一个虚函数。它在TranslateMessage和DispatchMessage之前触发。当它返回true时,消息不会再发送给TranslateMessage和DispatchMessage。当它返回false时,消息会继续发送给TranslateMessage和DispatchMessage。
对于窗口类,可以重写自己的PreTranslateMessage()。这样可以捕获消息来进行一些自定义处理。
它只能捕获经过消息队列的消息,不经过消息队列的消息无法捕获。
WindowProc函数也是可以捕获消息,对消息进行自定义处理的。
PreTranslateMessage和WindowProc的区别在于:
- 只有经过消息队列的消息PreTranslateMessage才捕获的到。比如SendMessage发出的本窗口消息,不经过消息队列,PreTranslateMessage没反应,但是WindowProc捕获得到。
- 根据上面写的消息循环部分,可以知道,每个线程都有一个消息循环,它从线程的消息对列中获取消息,然后对消息进行TranslateMessage()、DispatchMessage(),DispatchMessage会把消息发送给对应的窗口。PreTranslateMessage就是重写的位于TranslateMessage之前的函数。WindowProc是窗口用于接收消息的函数。这个两个函数的调用位置是不同的。
1:
MFC中PreTranslateMessage是GetMessage(…)函数的下一级操作,即GetMessage(…)从消息队列中获取消息后,交由PreTranslateMessage()处理,若其返回FALSE则再交给TranslateMessage和DispatchMessage处理(进入WindowProc);
如果用SendMessage, 则消息直接交到WindowProc处理,所以GetMessage不会取得SendMessage的消息,当然PreTranslateMessage也就不会被调用。
如果用PostMessage,则消息进入消息队列,由GetMessage取得,PreTranslateMessage就有机会进行处理。
2:
SendMessage要区分环境,如果是对本线程的窗口SendMessage,则不经过任何消息循环,也不放入消息队列,直接调用WindowProc,所以GetMessage和PreTranslateMessage都捕获不到;如果SendMessage是向其它线程或其它进程的窗口发消息,则消息进入消息队列,GetMessage和PreTranslateMessage能捕获到这个消息。
原文:VC++ PreTranslateMessage和WindowProc的使用总结
原文链接:https://blog.csdn.net/libaineu2004/article/details/90600433
参考
C语言/C++教程 大型源码案例分析:MFC消息系统的代码解析 易道云编程
SendMessage() 发出的消息 PreTranslateMessage() 不一定能接收到!
Microsoft Learn 关于消息和消息队列
MFC学习(一):MFC的消息循环 --讲解到位
VC++ PreTranslateMessage和WindowProc的使用总结