先来看windows消息机制:
首先系统(也就是windows)把来自硬件(鼠标,键盘等消息)和来自应用程序的消息 放到一个系统消息队列中去. 而应用程序需要有自己的消息队列,也就是线程消息队列,每一个线程有自己的消息队列,对于多线程的应用程序就有和线程数目相等的线程消息队列.
windows消息队列把得到的消息发送到线程消息队列,线程消息队列每次取出一条消息发送到指定窗口,不断循环直到程序退出.这个循环就是靠消息环
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
实现的 .GetMessage() 只是从线程消息中取出一条消息,TranslateMessage()把virtue key消息转化成character(键盘消息)息,如 VK_F1会转化成 WM_HELP,而 DispatchMessage 则把取出的消息发送到目的窗口.如果收到 WM_CLOSE消息则结束循环 ,发送 postqiutmessage(0),处理 WM_DESTROY销毁窗口 !
BOOL TranslateMessage( CONST MSG *lpMsg );如果消息被转换(即,字符消息被送到线程的消息队列中),返回非零值。
下面看看mfc中:
MFC消息控制流最具特色的地方是CWnd类的虚拟函数PreTranslateMessage(),通过重载这个函数,可以改变MFC的消息控制流程,甚至可以作一个全新的控制流出来。只有穿过消息队列的消息才受PreTranslateMessage()影响,采用SendMessage()或其他类似的方式向窗口直接发送的而不经过消息队列的消息根本不会理睬PreTranslateMessage()的存在。
是否调用TranslateMessage()和DispatchMessage()是由一个名称为PreTranslateMessage()函数的返回值决定的,如果该函数返回TRUE,则不会把该消息分发给窗口函数处理。
传给PreTranslateMessage()的消息是未经翻译过的消息,它没有经过TranslateMessage()处理。可以在该函数中使用 (pMsg->wParam==VK_RETURN)来拦截回车键。wParam中存放的是键盘上字符的虚拟码。
下面看一下GetMessage和PeekMessage:
PeekMessage 返回 TRUE 的条件是有消息,如果没有消息返回 FALSE 没有消息的时候立刻返回,所以cpu占用率高 非阻塞
GetMessage 返回 TRUE 的条件是有消息且该消息不为 WM_QUIT
返回 FALSE 的条件是有消息且该消息 为 WM_QUIT 在没有消息的时候等待消息,cpu当然低 阻塞
GetMessage不将控制传回给程序,直到从程序的消息队列中取得消息,但是PeekMessage总是立刻传回,而不论一个消息是否出现。
while (TRUE) { if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break ; TranslateMessage (&msg) ; DispatchMessage (&msg) ; } else { // 完成某些工作的其它行程序 } } return msg.wParam ;
代码中为何对wm_quit消息检查? 如果GetMessage接收到一个WM_QUIT消息,它将传回0,但是PeekMessage用它的传回值来指示是否得到一个消息,所以需要对WM_QUIT进行检查。
(尽管Windows文件上说,您不能用PeekMessage从消息队列中删除WM_PAINT消息,但是这并不是什么大不了的问题。毕竟,GetMessage并不从消息队列中删除WM_PAINT消息。从队列中删除WM_PAINT消息的唯一方法是令窗口显示区域的失效区域变得有效,这可以用ValidateRect和ValidateRgn或者BeginPaint和EndPaint对来完成。如果您在使用PeekMessage从队列中取出WM_PAINT消息后,同平常一样处理它,那么就不会有问题了。所不能作的是使用如下所示的程序代码来清除消息队列中的所有消息:
while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;这行叙述从消息队列中删除WM_PAINT之外的所有消息。如果队列中有一个WM_PAINT消息,程序就会永远地陷在while循环中。)
MFC通过CWinApp类中的Pumpmessage函数实现消息循环,但是实际的消息循环代码位于CWinThread中,CWinApp只是从CWinThread继承过来。其简化后的代码大概如下:
BOOL CWinThread::PumpMessage() { _AFX_THREAD_STATE *pState = AfxGetThreadState(); ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) if (!AfxPreTranslateMessage(&(pState->m_msgCur))) { ::TranslateMessage(&(pState->m_msgCur)); ::DispatchMessage(&(pState->m_msgCur)); } return TRUE; }
可以看到,PumpMessage在实际的TranslateMessage和DispatchMessage发生之前会调用AfxPreTranslateMessage,AfxPreTranslateMessage又会调用CWnd::WalkPreTranslateTree(虽然也会调用其他函数,但是这个最为关键),其代码如下:
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg) { ASSERT(hWndStop == NULL || ::IsWindow(hWndStop)); ASSERT(pMsg != NULL); // walk from the target window up to the hWndStop window checking // if any window wants to translate this message for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd)) { CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); if (pWnd != NULL) { // target window is a C window if (pWnd->PreTranslateMessage(pMsg)) return TRUE; // trapped by target window (eg: accelerators) } // got to hWndStop window without interest if (hWnd == hWndStop) break; } return FALSE; // no special processing }
可以看到,代码还是很直接的。从接受到消息的窗口层层往上遍历,并调用PretranslateMessage看是否返回TRUE,是则结束,否则继续。
这里有一个地方非常关键:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd) 这一句代码从当前AfxModuleThreadState拿到Permanent句柄表,从而找到hWnd对应的CWnd ;
后续:
MFC中PreTranslateMessage是GetMessage(...)函数的下一级操作,即GetMessage(...)从消息队列中获取消息后,交由PreTranslateMessage()处理,若其返回FALSE则再交给TranslateMessage和DispatchMessage处理(进入WindowProc);
如果用SendMessage, 则消息直接交到WindowProc处理,所以GetMessage不会取得SendMessage的消息,当然PreTranslateMessage也就不会被调用。
如果用PostMessage,则消息进入消息队列,由GetMessage取得,PreTranslateMessage就有机会进行处理。
2013,、7、24
jofranks 于南昌