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。