Thinking in MFC---消息机制1
引言
在使用较长时间的MFC之后,感觉自己需要将零散的MFC知识整合一下,所以开始推出这个系列的博文,首先就从MFC经典的消息机制入手,来介绍MFC是怎么运作的。这篇主要介绍一下消息机制中几个基础概念。
这篇主要介绍消息如何路由到主窗口。
一、WinMain函数
写过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。