進入MFC講壇的前言(二)
MFC的WinMain
使用MFC編程的程序員剛開始都會提出這樣一個問題:我的程序是從哪兒開始執行的?回答是:從WinMain()開始執行的。提出這樣的問題是由于在他們所編寫的MFC應用中看不到WinMain()函數。這個函數是隱藏在MFC框架中,MFC的設計者將它作得很通用(這主要得益于Window的消息驅動的編程機制,使得作一個通用的WinMain()很容易),因此在一般情況下,無需更改WinMain()的代碼,MFC的設計者也不提倡程序員修改WinMain()的代碼。在MFC中,實際實現WinMain()的代碼是AfxWinMain()函數(根據其前綴Afx就知道這是一個全局的MFC函數)。
一個Win32應用程序(或進程)是由一個或多個并發的線程組成的,其中第一個啟動的線程稱為主線程,在Window下,一般將線程分成兩大類,界面線程和工作線程,工作線程就是一般的線程,它沒有窗口,沒有消息隊列等,界面線程擁有一個或多個窗口,擁有一個消息隊列和其他專屬于界面線程的元素。在討論AfxWinMain()之前,首先要簡略提一下MFC中的兩個重要的類,CWinThread和CWinApp,CWinThread是用來封裝界面線程的類,CWinApp是從CWinThread派生而來的。在CWinThread中,有兩個很重要的虛擬函數InitInstance()和ExitInistance(),MFC的程序員應該對這兩個函數應該很熟悉。在CWinApp中,增加了另外一個虛擬函數InitApplication(),討論AfxWinMain()的主要目的是看這些函數是如何被調用的。
AfxWinMain()的代碼如下:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL); file://在win32下,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)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
AfxWinTerm();
return nReturnCode;
}
在上面的代碼中,AfxGetThread()返回的是當前界面線程對象的指針,AfxGetApp()返回的是應用程序對象的指針,如果該應用程序(或進程)只有一個界面線程在運行,那么這兩者返回的都是一個全局的應用程序對象指針,這個全局的應用程序對象就是MFC應用框架所默認的theApp對象(每次使用AppWizard生成一個SDI或MDI應用程序時,AppWizard都會添加CYourApp theApp這條語句,AfxGetApp()返回的就是這個theApp的地址)。
CWinApp::InitApplication(), CWinThread::InitInstance(), CWinThread::ExitInstance()是如何被調用的,從上面的代碼一看就知,我不再贅述。下面我們把焦點放在CWinThread::Run()上。
MFC的控制中心――CWinThread::Run()
說CWinThread::Run()是MFC的控制中心,一點也沒有夸大。在MFC中,所有來自于消息隊列的消息的分派都是在CWinThread::Run()函數中完成的,同AfxWinMain()一樣,這個函數也是對程序員是不可見的,其道理同AfxWinMain()的一樣。
首先要提的一點是,對每條從消息隊列取出來的消息,MFC根據消息的類型,按照某個特定的模式進行分發處理,這個分發模式是MFC自己定義的。固定的消息分發流程和在這個流程中的可動態改變其行為的虛擬函數就構成了MFC的消息分發模式。應用程序可以通過重載這些虛擬函數,來局部定制自己的的消息分發模式。正是通過這些虛擬函數,MFC為應用程序提供了足夠的靈活性。下面討論的所有代碼都來自于MFC源代碼中的threadcore.cpp文件,它們都是CWinThread的成員。
CWinThread::Run()的結構
CWinThread::Run()的代碼如下:
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));
}
ASSERT(FALSE); // not reachable
}
CWinThread::Run()的處理過程如下:
先根據空閑標志以及消息隊列是否為空這兩個條件判斷當前線程是否處于空閑狀態(這個“空閑”的含義同操作系統的含義不同,是MFC自己所謂的“空閑”),如果是,就調用CWinThread::OnIdle(),這也是我們比較熟悉的一個虛擬函數。
如果不是,從消息隊列中取出消息,進行處理,直到消息隊列為空。
在這里,我們發現,MFC不是調用GetMessage()從線程消息隊列中取消息,而是調用PeekMessage()。其原因在于,GetMessage()是一個具有同步行為的函數,如果消息隊列中沒有消息,GetMessage()會一直阻塞,使得線程處于睡眠狀態,直到消息隊列中有一條或多條消息,操作系統才會喚醒該線程,GetMessage()才會返回,如果線程處于睡眠狀態了,就不會使線程具有MFC所謂的“空閑”狀態了;而PeekMessage()則是一個具有異步行為的函數,如果消息隊列中沒有消息,它馬上返回0,不會導致線程處于睡眠狀態。
在上面的代碼中,有兩個函數值得探討,一個是空閑處理函數OnIdle(),另外一個是消息分發處理函數PumpMessage()。不要忽視CWinThread的OnIdle()函數,它作了很多有意義的事情。下面討論PumpMessage(),OnIdle()將在后面的章節里討論。
CWinThread::Run()的核心――CWinThread::PumpMessage()
標題強調了PumpMessage()的重要性,Run()是MFC的控制中心,而PumpMessage()又是Run()的核心,所以從MFC的真正控制中心是PumpMessage()。PumpMessage()的代碼極其簡單:
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
return FALSE;
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
首先,PumpMessage()調用GetMessage()從消息隊列中取一條消息,由于PumpMessage()是在消息隊列中有消息的時候才被調用的,所以GetMessage()會馬上返回,根據其返回值,判斷當前取出的消息是不是WM_QUIT消息(這個消息一般對是通過調用PostQuitMessage()放入線程消息隊列的),如果是,就返回FALSE,CWinThread::Run()該退出了,CWinThread::Run()直接調用CWinThread::ExitInstance()退出應用程序。在GetMessage()的后面是我們所熟悉的TranslateMessage()和DispatchMessage()函數。
可以看出,是否調用TranslateMessage()和DispatchMessage()是由一個名稱為PreTranslateMessage()函數的返回值決定的,如果該函數返回TRUE,則不會把該消息分發給窗口函數處理。
就我個人觀點而言,正是有了這個PreTranslateMessage(),才使得MFC能夠靈活的控制消息的分發模式,可以說,PreTranslateMessage()就是MFC的消息分發模式。
<三>MFC的特色――PreTranslateMessage()
經過層層扒皮,終于找到了CWinThread::Run()最具特色的地方,這就是PreTranslateMessage()函數。同前面使用SDK編寫的顯示”Hello, world!”程序的消息循環不同的地方在于,MFC多了這個PreTranslateMessage(),PreTranslateMessage()最先獲得了應用程序的消息處理權!下面我們對PreTranslateMessage()進行剝皮式分析。同前面一樣,首先看看實際的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 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
}
PreTranslateMessage()的處理過程如下:
首先判斷該消息是否是一個線程消息(消息的窗口句柄為空的消息),如果是,交給DispatchThreadMessageEx()處理。我們暫時不管DispatchThreadMessageEx(),它不是我們討論的重點。
調用CWnd::WalkPreTranslateTree()對該消息進行處理,注意該函數的一個參數是線程主窗口的句柄,這是PreTranslateMessage()的核心代碼,在后面會對這個函數進行詳細的分析。
對于非模式對話框,這特別的、額外的處理。
下面詳細討論一下CWnd::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 window checking
// 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 by target window (eg: accelerators)
}
// got to hWndStop window without interest
if (hWnd == hWndStop)
break;
}
return FALSE; // no special processing
}
CWnd::WalkPreTranslateTree()的所使用的策略很簡單,擁有該消息的窗口最先獲得該消息的處理權,如果它不想對該消息進行處理(該窗口對象的PreTranslateMessage()函數返回FALSE),就將處理權交給它的父親窗口,如此向樹的根部遍歷,直到遇到hWndStop(在CWinThread::PreTranslateMessage()中,hWndStop表示的是線程主窗口的句柄)。記住這個消息處理權的傳遞方向,是由樹的某個一般節點或葉子節點向樹的根部傳遞!
小結:
下面對這一章作一個小結。
MFC消息控制流最具特色的地方是CWnd類的虛擬函數PreTranslateMessage(),通過重載這個函數,我們可以改變MFC的消息控制流程,甚至可以作一個全新的控制流出來,在下面的一章會對MFC的實現作詳細介紹。
只有穿過消息隊列的消息才受PreTranslateMessage()影響,采用SendMessage()或其他類似的方式向窗口直接發送的而不經過消息隊列的消息根本不會理睬PreTranslateMessage()的存在
傳給PreTranslateMessage()的消息是未經翻譯過的消息,它沒有經過TranslateMessage()處理,在某些情況下,要仔細處理,以免漏掉消息。