//这里讲述MFC的消息循环,消息泵。先看看程序启动时,怎么进入消息循环的:
//thrdcore.cpp // main running routine untilthread exits int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle timestate BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messagesuntil a WM_QUIT message is received. for (;;) { // phase1: checkto see if we can do idle work while (bIdle &&!::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE)) { // call OnIdle while in bIdlestate if(!OnIdle(lIdleCount++)) bIdle =FALSE; // assume "no idle" state } // phase2: pump messages whileavailable do { // pump message, but quit onWM_QUIT if (!PumpMessage()) returnExitInstance(); // reset "no idle" state afterpumping "normal" message if(IsIdleMessage(&m_msgCur)) { bIdle =TRUE; lIdleCount =0; } } while(::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE)); } //无限循环,退出条件是收到WM_QUIT消息。 ASSERT(FALSE); // not reachable }
if (!PumpMessage()) returnExitInstance();
VOID PostQuitMessage( int nExitCode)
//thrdcore.cpp BOOLCWinThread::PumpMessage() { ASSERT_VALID(this); //如果是WM_QUIT就退出函数(returnFALSE),这将导致程序结束. if(!::GetMessage(&m_msgCur, NULL, NULL, NULL)){ #ifdef _DEBUG if (afxTraceFlags& traceAppMsg) TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n"); m_nDisablePumpCount++; //application must die // Note:prevents calling message loop things in 'ExitInstance' // willnever be decremented #endif return FALSE; } #ifdef _DEBUG if (m_nDisablePumpCount != 0) { TRACE0("Error:CWinThread::PumpMessage called when not permitted.\n"); ASSERT(FALSE); } #endif #ifdef _DEBUG if (afxTraceFlags & traceAppMsg) _AfxTraceMsg(_T("PumpMessage"),&m_msgCur); #endif // process this message if (m_msgCur.message != WM_KICKIDLE&&!PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); //键转换 ::DispatchMessage(&m_msgCur); //派送消息 } return TRUE; }
//thrdcore.cpp BOOL CWinThread::PreTranslateMessage(MSG* pMsg) { ASSERT_VALID(this); // 如果是线程消息,那么将会调用线程消息的处理函数 if (pMsg->hwnd == NULL&&DispatchThreadMessageEx(pMsg)) return TRUE; // walk from target to mainwindow CWnd* pMainWnd = AfxGetMainWnd(); if(CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(),pMsg)) return TRUE; // in case of modeless dialogs,last chance route through main // window's acceleratortable if (pMainWnd != NULL) { CWnd* pWnd =CWnd::FromHandle(pMsg->hwnd); if(pWnd->GetTopLevelParent() != pMainWnd) returnpMainWnd->PreTranslateMessage(pMsg); } returnFALSE; // no specialprocessing }
//dlg_5Dlg.cpp BOOL CDlg_5App::InitInstance() { AfxEnableControlContainer(); #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFCstatically #endif CDlg_5Dlgdlg; //定义一个对话框对象 m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); //对话框的消息循环在这里面开始 if (nResponse == IDOK) { // TODO: Place code here tohandle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here tohandle when the dialog is // dismissed withCancel } // Since the dialog has beenclosed, return FALSE so that we exit the // application, rather than start the application's messagepump. return FALSE; }
int CWnd::RunModalLoop(DWORD dwFlags) { ASSERT(::IsWindow(m_hWnd)); // window must be created ASSERT(!(m_nFlags & WF_MODALLOOP)); // window mustnot already be in modal state // for tracking the idle timestate BOOL bIdle = TRUE; LONG lIdleCount = 0; BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE)&& !(GetStyle() &WS_VISIBLE); HWND hWndParent = ::GetParent(m_hWnd); m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL); MSG* pMsg =&AfxGetThread()->m_msgCur; // acquire and dispatch messagesuntil the modal state is done for (;;) { ASSERT(ContinueModal()); //phase1: check to see if we can do idle work while (bIdle&& !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE)) { ASSERT(ContinueModal()); // show thedialog when the message queue goes idle if(bShowIdle) { ShowWindow(SW_SHOWNORMAL); UpdateWindow(); bShowIdle = FALSE; } // callOnIdle while in bIdle state if(!(dwFlags & MLF_NOIDLEMSG)&& hWndParent != NULL&& lIdleCount == 0) { // send WM_ENTERIDLE to the parent ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX,(LPARAM)m_hWnd); } if ((dwFlags& MLF_NOKICKIDLE) || !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++)) { // stop idle processing next time bIdle = FALSE; } } // phase2: pump messages whileavailable do { ASSERT(ContinueModal()); // pumpmessage, but quit on WM_QUIT //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。 if(!AfxGetThread()->PumpMessage()) { AfxPostQuitMessage(0); return -1; } // show thewindow when certain special messages rec'd if(bShowIdle && (pMsg->message == 0x118 ||pMsg->message == WM_SYSKEYDOWN)) { ShowWindow(SW_SHOWNORMAL); UpdateWindow(); bShowIdle = FALSE; } if(!ContinueModal()) goto ExitModal; // reset "noidle" state after pumping "normal" message if(AfxGetThread()->IsIdleMessage(pMsg)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(pMsg,NULL, NULL, NULL, PM_NOREMOVE)); } //无限循环 ExitModal: m_nFlags &=~(WF_MODALLOOP|WF_CONTINUEMODAL); return m_nModalResult; }
if (!ContinueModal()) goto ExitModal;
BOOL CWnd::ContinueModal() { return m_nFlags & WF_CONTINUEMODAL; }
//wincore.cpp void CWnd::EndModalLoop(intnResult) { ASSERT(::IsWindow(m_hWnd)); // this result will be returnedfrom CWnd::RunModalLoop m_nModalResult = nResult; // make sure a message goes throughto exit the modal loop if (m_nFlags & WF_CONTINUEMODAL) { m_nFlags &=~WF_CONTINUEMODAL; PostMessage(WM_NULL); } }
LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l) { //调用CWnd::UpdateDialogControls更新用户界面 UpdateDialogControls(this, TRUE); return 0; }
while(1) { PostQuitMessage(0); //程序照样down. }
要不然程序就不会响应了,这不是我们所希望的。
所以说,消息循环放在程序的什么的地方都基本上是过的去的,比如放在DLL里面。
但是,最好在任何时候,只有一个消息循环在工作(其他的都被阻塞了)。
然后,我们要作好的一件事情,就是怎么从消息循环中退出!
当然用WM_QUIT是可以拉~(PostThreadMessage也是个好主意),
这个消息循环退出后,可能程序退出,也可能会激活另外一个被阻塞的消息循环,程序继续运行。
NOTE: 让windows系统知道创建一个线程的唯一方法是调用APICreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)。
好像windows核心编程说,在win2000下,系统用CreateRemoteThread函数来创建线程,CreateThread在内部调用CreateRemoteThread。
不过这不是争论的焦点,至少win98下CreateRemoteThread并不能正常工作,还是CreateThread主持大局。
3,在整个消息循环的机制中,还必须谈到窗口函数的可重入性。
什么意思?就是窗口函数(他是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。
比如在窗口过程中,向自己的窗口SendMessage(***);那么执行过程是怎样的?
我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。
那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程序不就互相死锁了吗?其实是不会的。
windows设计一套适合SendMessage的算法,
他判断如果发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入),
并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子,我们调用SendMessage(***)发送消息到本窗口,
那么窗口过程再次被调用,处理完消息之后将结果返回,然后SendMessage之后的程序接着执行。
对于非队列消息,如果没有窗口重入,不知道会是什么样子。
NOTE: 由于窗口的可重入性。
在win32SDK程序中应尽量少用全局变量和静态变量,因为在窗口函数执行过程中可能窗口重入,
如果重入后将这些变量改了,但你的程序在窗口重入返回之后继续执行,可能就是使用已经改变的全局或静态变量。
在MFC中(所有窗口的窗口函数基本上都是AfxWndProc),按照类的思想进行了组织,一般变量都是类中的,好管理的多。
4,MFC中窗口类(比如C**View,CFrameWnd等)中的MessageBox函数,以及AfxMessageBox函数都是阻塞原有的消息循环的。
由消息框内部的一个消息循环来从消息队列中读取消息,并派送消息(和模式对话框类似)。
实际上,这些消息函数最终调用的是::MessageBox,它在消息框内部实现了一个消息循环(原有的主程序消息循环被阻塞了)。
论坛中碰到过几次关于计时器和消息框的问题,看下面的代码:
void CTest_recalclayoutView::OnTimer(UINTnIDEvent) { // TODO: Add your message handler code here and/or calldefault MessageBox("abc"); while(1); //设计一个死循环 CView::OnTimer(nIDEvent); }
咱让OnTimer大约5秒钟弹出一个消息框。那么,消息框不断的被弹出来,只要消息框不被关闭,那么程序就不会进入死循环。
实际上,每次弹出对话框,都是最上层的那个消息框掌握着消息循环,其他的消息循环被阻塞了。
只要不关闭最上面的消息框,while(1);就得不到执行。
5,消息循环在很多地方都有应用。
比如应用在线程池中。一个线程的执行周期一般在线程函数返回之后结束,那么怎么延长线程的生命周期呢?
6,在单线程程序中,如果要执行一个长时间的复杂操作而且界面要有相应的话,可以考虑用自己的消息泵。
比如,可以将一个阻塞等待操作放在一个循环中,并将超时值设置得比较小,然后每个等待的片段中用消息泵继续消息循环,使界面能够响应用户操作。
等等之类,都可以应用消息泵(调用一个类似这样的函数):
BOOL CChildView::PeekAndPump() { MSG msg; while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) { if(!AfxGetApp()->PumpMessage()) { ::PostQuitMessage(0); return false; } } return true; }
这种思想主要的特点表现在迎合MFC整个框架上,为整个框架服务,为应用和功能服务。这是我的理解。呵呵~
///////////////////////////////////////////////////////////////////////////////////////////
MFC程序的执行顺序依次是:theApp全局对象定义处、TestApp构造函数、WinMain。
程序在加载main函数之前,会先为全局变量和全局对象分配内存空间。
对于MFC程序来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象,该对象就表示了应用程序本身。
theApp对象的构造函数CtestApp在调用之前,会调用其父类CWinApp的构造函数,从而就把我们程序自己创建的类与Microsoft提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。
AfxWinMain函数:WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
AfxWinMain调用AfxGetThread函数获得一个CWinTread类型的指针。
接着AfxWinMain调用AfxGetApp函数获得一个CWinApp类型的指针。
由以下代码可以看出:AfxGetThread函数返回的就是AfxGetApp函数的结果。
CWinThread* AFXAPI AfxGetThread()
{
// check for current thread in module thread state
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
CWinThread* pThread = pState->m_pCurrentWinThread;
// if no CWinThread for the module, then use the global app
if (pThread == NULL)
pThread = AfxGetApp();
return pThread;
}
因此,AfxWinMain函数中的pThread和pApp这两个指针是一致的。
AfxGetApp函数返回的是在CWinApp构造函数中保存的this指针(详见CWinApp类定义的源文件:appcore.cpp)。
对本Test程序来说,这个this指针实际上指向的是CTestApp的对象:theApp。也就是说,pThread和pApp所指向的都是CTestApp类的对象,即theApp全局对象。
InitInstance函数:
pThread和pApp调用了三个函数(InitApplication、InitInstance和Run)去完成Win32程序所需要的几个步骤:设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环以及窗口过程函数。
pApp首先调用InitApplication函数完成MFC内部管理方面的工作。
接着,调用pThread的InitInstance函数(其实是调用从CWinApp派生的应用程序类CTestApp中的虚函数InitInstance)。
MFC框架窗口
1.设计和注册窗口:MFC已经为我们预定义了一些默认的标准窗口类,只需呀选择所需的窗口类,然后注册就可以了。
窗口类的注册由AfxEndDeferRegisterClass函数完成(AfxEndDeferRegisterClass函数首先判断窗口类的类型,然后赋予其相应的类名,这些类名是MFC预定义的,然后调用AfxRegisterClass函数注册窗口类)。
AfxRegisterClass函数首先获得窗口类的信息,如果该窗口已经注册,则直接返回一个真值;如果尚未注册,就调用RegisterClass函数注册该窗口类(注册窗口类使用的函数其实和Win32 SDK编程中所使用的函数一致)。
我们所创建的这个MFC应用程序Test,实际上有两个窗口。
其中之一:CMainFrame::PreCreateWindow,这是在窗口产生之前被调用的。CMainFrame::PreCreateWindow函数又调用了AfxDeferRegisterClass,而AfxDeferRegisterClass实际上是一个宏,指向AfxEndDeferRegisterClass(前面提到,此函数的功能就是注册窗口类)。
2.创建窗口:
窗口的创建是由CWnd类的CreateEx函数实现的。(声明:AFXWin.h实现:WINCORE.CPP)
(在MFC的底层代码中CFrameWnd::Create调用了上述的CreateEx函数,而CFrameWnd::LoadFrame又调用CFrameWnd::Create函数。此过程请自行跟踪。)
CWnd::CreateEx调用CMainFrame::PreCreateWindow(PreCreateWindow是一个虚函数,所以这里实际上调用的是子类,即CMainFrame::PreCreateWindow。这里再次调用此函数是为了在产生窗口之前让程序员有机会修改程序的外观。例如,去掉窗口最大化按钮等)
3.显示和更新窗口
CTestApp::m_pMainWnd
m_pMainWnd是一个CWnd类型并且保存了应用程序框架窗口对象的指针,也就是说m_pMainWnd变量是一个指向CMainFrame对象的指针。
CTestApp::InitInstance函数实现内部有如下两行代码:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
这两行代码实现了窗口的显示和更新。
消息循环
CWinThread::Run函数完成消息循环。
该函数由AfxWinMain::pThread函数调用。
形式:pThread->Run();
CWinThread::Run函数主要是一个for循环,该循环在收到一个WM_QUIT消息时退出。在此循环中调用了一个PumpMessage函数。
窗口过程函数
AfxEndDeferRegisterClass函数源程序:wndcls.lpfnWndProc = DefWindowProc
这里指定的一个默认窗口过程DefWindowProc。但实际上MFC程序并不是把所有的消息都交给DefWindowProc这一默认窗口过程来处理。而是采用了一种“消息映射机制”。
///////////////////////////////////////////////////////////////////////////////////////////
梳理全过程
1.首先利用全局应用程序对象theApp启动应用程序。正是产生了这个全局对象,基类CWinApp中的this指针才能指向这个对象。如果没有这个全局对象,程序在编译的时候不会出错,但在运行时就出错。
2.调用全局应用程序对象的构造函数,从而就会先调用其基类CWinApp的构造函数。后者完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。
3.进入WinMain函数。在AfxWinMain函数中可以获取子类(对Test程序来说,就是CTestApp类)的指针,利用此指针调用虚函数:InitInstance,根据多态性原理,实际上调用的是子类(CTestApp)的InitInstance函数。后者完成一些应用程序的初始化工作,包括窗口类的注册、创建,窗口的显示和更新。期间会多次调用CreateEx函数,因为一个单文档MFC应用程序有多个窗口,包括框架窗口、工具条、状态条等。
4.进入消息循环。虽然也设置了默认的窗口过程函数,但是,MFC应用程序实际上采用消息影射机制来处理各种消息的。当收到WM_QUIT消息时,退出消息循环,程序结束。
///////////////////////////////////////////////////////////////////////////////////////////