在使用较长时间的MFC之后,感觉自己需要将零散的MFC知识整合一下,所以开始推出这个系列的博文,首先就从MFC经典的消息机制入手,来介绍MFC是怎么运作的。这篇主要介绍一下消息机制中几个基础概念。
这篇主要介绍消息如何路由到主窗口。
写过win32程序,肯定只要我们要展示一个窗口,需要这个入口函数,这个就像控制台程序中main函数,是程序的入口。但是在MFC项目创建之后,我们在项目工程却找不到这个函数。
其实MFC已经将这个函数内部写好了,具体在下面这个文件中。
Microsoft Visual Studio10.0\VC\atlmfc\src\mfc\winmain.cpp
//
以下winmain函数中的内容,这些代码中的大部分我们暂时不做详细介绍,在之后的篇章中会来讨论这些代码。
ASSERT(hPrevInstance == NULL); int nReturnCode = -1; CWinThread*pThread = AfxGetThread(); CWinApp*pApp = AfxGetApp(); // AFX internal initialization if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine,nCmdShow)) goto InitFailure; // App global initializations (rare) if (pApp != NULL &&!pApp->InitApplication()) goto InitFailure; // Perform specific initializations if (!pThread->InitInstance()) { if (pThread->m_pMainWnd != NULL) { TRACE(traceAppMsg,0, "Warning: Destroying non-NULLm_pMainWnd\n"); pThread->m_pMainWnd->DestroyWindow(); } nReturnCode= pThread->ExitInstance(); goto InitFailure; } nReturnCode =pThread->Run(); InitFailure: #ifdef _DEBUG // Check for missing AfxLockTempMap calls if (AfxGetModuleThreadState()->m_nTempMapLock !=0) { TRACE(traceAppMsg,0, "Warning: Temp map lock count non-zero(%ld).\n", AfxGetModuleThreadState()->m_nTempMapLock); } AfxLockTempMaps(); AfxUnlockTempMaps(-1); #endif AfxWinTerm(); return nReturnCode;
上诉代码中第27行代码,我们需要找的消息循环就在Run函数中,接下来就看一下这个Run函数。
这个类微软用来封装MFC的线程。
这里我们主要来讨论这个类中的run函数
virtual int CWinThread::Run() { ASSERT_VALID(this); BOOL bIdle =TRUE; LONGlIdleCount = 0; //这里就是消息循环 for (;;) { // 如果线程处于空闲状态,消息队列中也没有消息,做空闲处理 while(bIdle && !::PeekMessage(&m_msgCur,NULL, NULL, NULL, PM_NOREMOVE)) { // PeekMessage()用于检查消息队列是否为空,但并不处理消息 if (!OnIdle(lIdleCount++)) bIdle= FALSE; } do// 空闲处理接触,进行消息处理 { if (!PumpMessage())// 消息处理,如果是WM_QUIT就退出消息循环 return ExitInstance(); if (IsIdleMessage(&m_msgCur)) { bIdle= TRUE; lIdleCount= 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE)); } ASSERT(FALSE);// }
这里先说明,以上代码包括下面的代码都是笔者从网上找到的,所以对于其准确性不能够保证,但是作为介绍性说明,应该是没有问题的。
我们按照函数执行顺序往下看,首先我们发现了一个死循环,这个死循环便是我们要的消息循环。
接下来我们便是idle状态的处理,没有消息处理的时候,线程进入idle状态。其中PeekMessage,便是用来判断消息队列中是否有消息。
再往下看,我们会发现一个叫PumpMessage,这个函数便是这里的一个关键函数(从run函数的代码中看,好像也没有其对队消息进行处理的函数了)。
接下来就看一下PumpMessage函数的源码
BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) return FALSE; if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; }
按照执行顺序看,我们会注意到GetMessage,这个函数这里暂时不做介绍,就是从线程的消息队列中获取消息,并把消息内容放入一个结构的对象中(这里就放入msgCur)。
接下来便是判断消息是否是idle消息,如果不是就交给PreTranslateMessage,这个函数又是一个关键函数,所以我们有必要再次去找到它的源码进行分析。
BOOL CWinThread::PreTranslateMessage(MSG* pMsg) { ASSERT_VALID(this); // if this is a thread-message, short-circuit this function if (pMsg->hwnd == NULL &&DispatchThreadMessageEx(pMsg)) return TRUE; // walk from target to main window CWnd*pMainWnd = AfxGetMainWnd(); if(CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)) return TRUE; // in case of modeless dialogs, last chance route throughmain // window'saccelerator table if (pMainWnd != NULL) { CWnd*pWnd = CWnd::FromHandle(pMsg->hwnd); if (pWnd->GetTopLevelParent() != pMainWnd) return pMainWnd->PreTranslateMessage(pMsg); } return FALSE; // no special processing }
我们按照函数的执行顺序来看,首先判断一下这个消息是否是发给窗口的。如果不是,那么就是一个线程消息,就交给DispatchThreadMessageEx(这里也暂时不介绍这个函数,不是这篇讨论的重点)。
接下面,我们获得主窗口对象,就把消息交给WalkPreTranslateTree处理。
继续探讨,我们有需要找到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 windowchecking // 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 bytarget window (eg: accelerators) } // got to hWndStop window without interest if (hWnd == hWndStop) break; } return FALSE; // no specialprocessing }
略去一些空值的检查,我们看到消息最后是交给了主窗口的PreTranslateMessage函数处理了。接下来就交给主窗口进行消息处理了。
这里我们就大致了解了一个消息怎么路由到主窗口的流程。
开始编程不久,便接触到了MFC。用了MFC写了自己第一个视窗程序,写了第一个游戏,在学校课题演示的时候也是喜欢用MFC做演示。
现在工作了,项目的界面部分也在使用MFC开发的,值得庆幸的是,现在微软对于MFC的界面做了很大的改善,想起刚开始不断重写控件类,那个真是记忆犹新。
在平时编程的时候,VS这个集成工具确实很方便。但现在想把一些MFC零零碎碎的知识整合起来。所以这个系列可能会深入地去介绍MFC。