转自(http://blog.csdn.net/ljx0305/article/details/2155207)
首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage),消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情。在MFC的应用程序中(应用程序类基于CWinThread继承),必须要有一个消息循环,他的作用是从应用程序的消息队列中读取消息,并把它派送出去(::DispatchMessage)。而消息路由是指消息派送出去之后,系统(USER32.DLL)把消息投递到哪个窗口,以及以后消息在窗口之间的传递是怎样的。
消息分为队列消息(进入线程的消息队列)和非队列消息(不进入线程的消息队列)。对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息;还有例如:WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统负责把消息加入到相应线程的消息队列中,于是就有了消息循环(从消息队列中读取并派送消息)。还有一种是非队列消息,他绕过系统队列和消息队列,直接将消息发送到窗口过程。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。创建窗口时发送WM_CREATE消息。在后面你将看到,MS这么设计是很有道理的,以及他的整套实现机制。
这里讲述MFC的消息循环,消息泵。先看看程序启动时,怎么进入消息循环的:
_tWinMain ->AfxWinMain ->AfxWinInit ->CWinThread::InitApplication ->CWinThread::InitInstance ->CWinThread::Run |
//thrdcore.cpp // main running routine until thread exits int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "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()) return ExitInstance(); |
在PumpMessage中,如果收到WM_QUIT消息,那么返回FALSE,所以ExitInstance()函数执行,跳出循环,返回程序的退出代码。所以,一个程序要退出,只用在代码中调用函数
VOID PostQuitMessage( int nExitCode )。指定退出代码nExitCode就可以退出程序。
下面讨论一下这个函数Run的流程,分两步:
1,第一个内循环phase1。bIdle代表程序是否空闲。他的意思就是,如果程序是空闲并且消息队列中没有要处理的消息,那么调用虚函数OnIdle进行空闲处理。在这个处理中将更新UI界面(比如工具栏按钮的enable和disable状态),删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。OnIdle是可以重载的,你可以重载他并返回TRUE使消息循环继续处于空闲状态。
NOTE:MS用临时对象是出于效率上的考虑,使内存有效利用,并能够在空闲时自动撤销资源。关于由句柄转换成对象,可以有若干种方法。一般是先申明一个对象obj,然后使用obj.Attatch来和一个句柄绑定。这样产生的对象是永久的,你必须用obj.Detach来释放对象。
2,第二个内循环phase2。在这个循环内先启动消息泵(PumpMessage),如果不是WM_QUIT消息,消息泵将消息发送出去(::DispatchMessage)。消息的目的地是消息结构中的hwnd字段所对应的窗口。
//thrdcore.cpp BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); //如果是WM_QUIT就退出函数(return FALSE),这将导致程序结束. 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' // will never 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 main window CWnd* pMainWnd = AfxGetMainWnd(); if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)) return TRUE; // in case of modeless dialogs, last chance route through main // window's accelerator table if (pMainWnd != NULL) { CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd); if (pWnd->GetTopLevelParent() != pMainWnd) return pMainWnd->PreTranslateMessage(pMsg); } return FALSE; // no special processing } |
//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 MFC statically #endif CDlg_5Dlg dlg; //定义一个对话框对象 m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); //对话框的消息循环在这里面开始 if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application's message pump. return FALSE; } |
int CWnd::RunModalLoop(DWORD dwFlags) { ASSERT(::IsWindow(m_hWnd)); // window must be created ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state // for tracking the idle time state 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 messages until 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 the dialog when the message queue goes idle if (bShowIdle) { ShowWindow(SW_SHOWNORMAL); UpdateWindow(); bShowIdle = FALSE; } // call OnIdle 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 while available do { ASSERT(ContinueModal()); // pump message, but quit on WM_QUIT //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。 if (!AfxGetThread()->PumpMessage()) { AfxPostQuitMessage(0); return -1; } // show the window 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 "no idle" 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(int nResult) { ASSERT(::IsWindow(m_hWnd)); // this result will be returned from CWnd::RunModalLoop m_nModalResult = nResult; // make sure a message goes through to 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; } |
CMyDlg* pdlg = new CMyDlg; pdlg ->Create(IDD_DIALOG1); pdlg->ShowWindow(SW_SHOW); MessageBox("abc"); |
while(1) { PostQuitMessage(0); //程序照样down. } |
void CTest_recalclayoutView::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default MessageBox("abc"); while(1); //设计一个死循环 CView::OnTimer(nIDEvent); } |
BOOL CChildView::PeekAndPump() { MSG msg; while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) { if(!AfxGetApp()->PumpMessage()) { ::PostQuitMessage(0); return false; } } return true; } |