摘要:本文章主要以MFC程序的執行流程、執行順序等執行過程的剖析做出的結論,下面一起來看看原文的具體介紹。
一、MFC介紹
微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是微軟公司提供的一個類庫(class libraries),以C++類的形式封裝了Windows API,并且包含一個應用程序框架,以減少應用程序開發人員的工作量。其中包含大量Windows句柄封裝類和很多Windows的內建控件和組件的封裝類。
二、MFC程序執行過程剖析
1)我們知道在WIN32API程序當中,程序的入口為WinMain函數,在這個函數當中我們完成注冊窗口類,創建窗口,進入消息循環,最后由操作系統根據發送到程序窗口的消息調用程序的窗口函數。而在MFC程序當中我們不在能找到類似WinMain這樣的程序入口,取而代之的是一系列派生類的聲明和定義以及一個沖CWinApp類派生而來的類的全局對象。CWinApp類被稱之為應用程序對象,在一個MFC程序當中只允許有一個應用程序對象。由于CWinApp的派生對象是全局的,因此這個對象的構造函數會在所有的其他代碼運行之前被調用,而由于CWinApp類當中包含了HWND、HINSTANCE等句柄的存在,其構造函數就執行了對這些成員數據的初始化操作,這里的所謂初始化僅僅是把所有的句柄對象賦值為NULL。
2)在調用完CWinApp的構造函數以后由連接器向程序內自動鏈接的AfxWinMain函數將被調用,而這個函數可以被看作MFC程序的入口函數。在這個函數當中調用全局AfxGetApp()函數獲得應用程序對象,這時將調用AfxInit全局函數,這個函數的功能是使用操作系統傳遞給AfxWinMain函數的參數初始化應用程序對象當中的相關句柄數據成員。
3)之后AfxWinMain函數調用CWinApp::InitApplication成員函數,這個成員函數用來初始化應用程序對象當中的關于文檔部分的內容。
4)隨后調用CWinApp::InitInstance成員函數,在這個成員函數當中,使用new操作在堆上聲明一個框架窗口對象,由此導致框架窗口對象的構造函數被調用,在框架窗口構造函數當中調用Create函數來創建窗口,而調用的Create函數一般將WNDCLASS參數設置成NULL,這樣就由MFC內部調用PreCreateWindow函數,在這個函數當中由MFC注冊幾個默認的WNDCLASS供框架窗口的Create使用。這時程序控制權交還給CWinApp::InitInstance成員函數內部,由這個函數調用CWnd::ShowWindow顯示窗口并且調用CWnd::UpdateWindow向窗口發送WM_PAINT消息。調用完CWinApp::InitInstance成員函數后由AfxWinMain函數調用CWinApp::Run成員函數,并由這個函數來創建和處理消息循環,并且在沒有消息的時候處理OnIdle空閑處理。至此整個程序的創建過程完成。
5)在程序的運行過程當中,由操作系統源源不斷的發送消息給應用程序,并且由CWinApp::Run當中的消息循環處理并且分發給相關的窗口對象的DefWindowProc成員函數,并由這個成員函數查詢窗口對象的消息映射表,如果查到對應項,則由登記在消息映射表當中的類成員函數處理,否則則按照Message Route當中的順序象父層類發送。
6)在消息運行結束,用戶按下關閉按鈕后,操作系統向程序發送WM_CLOSE消息,默認狀況下程序調用DestoryWindow并且發送WM_DESTORY消息,應用程序接受到這個消息以后的默認操作是調用PostQuitMessage函數,由這個函數發送WM_QUIT消息。當程序對象接受到WM_QUIT消息后消息循環結束,由AfxWinMain函數調用AfxTerm函數清理程序使用過的資源并且結束整個程序。
三、MFC程序的執行順序
MFC只是對WIN32的API進行了封裝,所以MFC的本質還是WIN32程序。有了這層封裝,我們看不到WIN32的WinMain函數,也就不清楚MFC程序的啟動過程。雖然我們沒有看到WinMain函數,但不代表沒有WinMain函數,這個函數位于*\VC\atlmfc\src\mfc目錄的appmodul .cpp文件中有一個_tWinMain函數, _tWinMain函數調用了WinMain.CPP文件中的AfxWinMain函數。
tWinMain函數實現:
extern “C” int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}1234567
AfxWinMain函數實現:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
// 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-NULL m_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;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445
_tWinMain和WinMain的聲明是一致的,但是_tWinMain不是最先執行的,因為整個程序一開始是初始化全局變量,這里的全局變量有WinApp類型的theApp,初始化theApp就是執行CWinApp的構造函數。在這個AfxWinMain函數里面調用了pThread-》InitInstance()函數,InitInstance函數是CWinApp類(CWinApp繼承于CWinThread)的虛函數,所以這里就調用派生類的InitInstance函數,用戶一般在這個函數里面創建對話框,單文檔,多文檔界面。執行完InitInstance函數后,就會執行CWinApp類的run函數(*\VC\atlmfc\src\mfc\thrdcore.cpp),這個函數一個死循環,不斷地從的消息隊列中讀取消息。代碼如下:
int CWinThread::Run()
{
ASSERT_VALID(this);
_AFX_THREAD_STATE* pState = AfxGetThreadState();
// 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(&(pState-》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))
if (IsIdleMessage(&(pState-》m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&(pState-》m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
}
}123456789101112131415161718192021222324252627282930313233343536373839
小結
MFC程序的執行順序和Win32的執行順序完全一樣:初始化全局變量(theApp),執行WinMain函數(AfxWinMain),創建及注冊窗口(在InitInstance函數中創建對話框,單文檔,多文檔窗口),消息循環(Run函數)
四、MFC程序的執行流程
對于理解MFC程序執行流程,我覺得理解new 、虛擬函數調用、構造和析構函數調用(http://blog.csdn.net/misskissc/article/details/8549254)次序、類指標結論幾個地方對此很有幫助。剛好在學習MFC程序的過程中也正好去學習了這幾個方面(或者說是先去查詢式的學習了這幾個方面之后覺得對理解MFC程序執行流程有幫助)。
1.new
new和malloc函數都可以為變量申請一段某種類型的動態存儲空間,new在申請動態空間外還會引發申請空間對象構造函數的運行。當然,有new就需要在需要的地方有delete,這是一種必備的習慣。
2.虛擬函數的調用
使用派生類對象訪問類的成員函數時,若此函數為非虛擬函數時,訪問的函數都是基類成員函數的地址;若訪問的成員函數為虛擬函數(前面加了virtual關鍵字)時,若在派生類中沒有申明(修改)則調用的是基類(或者某派生類(當此派生類改變了此虛擬函數時))中的函數地址,若在派生類修改了虛擬函數,則調用的就是派生類的成員函數。
3.構造函數和析構函數的調用次序
構造函數和析構函數的調用次序從一定程度上反應出MFC程序執行流程。建構式(運行應用程序類的對象的構造函數)是早于程序運行入口點的,即應用程序類對象的構造函數的運行要比AfxWinMain函數的運行要早。當然,析構函數是在相應情況下(delete發生、局部運行完畢、程序運行結束時)發生。而且構造函數的運行遵循一定的方式(構造函數析構函數調用次序),析構函數的運行與構造函數的運行相反。
4.類指標相關結論
如果以一個基礎類別之指標指向衍生類別之物件,那么該指標只能呼叫基礎類所定義的函式。如果一個衍生類指標指向一個基礎類別物件,必須先做明確的轉型動作(對基礎類之物件取地址&對派生類指標賦值)。這種做法很危險。如果基礎類別和衍生類別都定義了相同名之成員函式,那么透過物件指標呼叫成員函式時,到底呼叫到哪一個函式,必須視該指標原始性別而定,而不是視指標實際所指之物件的性別而定。
五、簡析MFC程序執行流程
撇開所有的頭文件中對類聲明、派生的代碼,我們到應用程序類源代碼文件(CMy*.cpp)定義應用程序類對象的地方,見圖1
圖1.《深入淺出 MFC》一書中的MFC程序執行流程分析圖
MFC程序執行流程跟標號增大的方向一致:
當定義應用程序類對象時,在程序進入AfxWinMain()函數前,應用程序類對象的構造函數先運行,構造應用程序類對象。
進入程序運行的入口點AfxWinMain()函數中,AfxGetApp()函數返回指向當前應用程序的單一的CWinApp對象的指針。根據類指標相關結論可知,除了被修改的虛擬函式外,用指針pApp訪問的函數都是訪問的CWinApp中的成員函式。所以用pApp-》InitApplication() [及Run] 調用的函式為CWindApp::InitApplication() [及CWindApp::Run()] ;因為InitIntance()函數被改變,因此而pApp-》InitIntance()調用的卻是派生類中(假設為CMyWindApp)的函式(CMyWindApp::InitIntance()),而且InitIntance()函數內的內容會被自動執行,一般這里的代碼為跟生成窗口有關。
這里是關于初始化的函數,作為初學者,這里可以先跳過。容日后究。
這就是在調用派生類CMyWindApp的成員函數,雖然這個調用的語句我們看不到。這里涉及虛擬函數妙用的內容哦,派生類中被修改的虛擬函數將會被調用。
(5-8語句)這是在對虛擬函數InitIntance()進行改寫,在改寫的過程中很可能依據函數的調用方式(虛擬函數,非虛擬函數的調用方式)再調用其它的函數。其實函數的調用占MFC應用程序執行流程的很大一部分,對于函數的調用,這里添加了一些規則,如此就構成了整個的運行。
(9語句)pApp-》Run()就是檢測是否有消息發生的主消息循環事件函數,當有任何規定的事件發生它都會檢測到,并通過一定方式調用到相應的函數,具體內容可以后深究。
這個過程只是在定義應用程序類對象時MFC內部機制的簡單剖析,其實在接收到用戶信息、調用用響應的事件函數(DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC巨集)等這些過程都是很有研究價值的,對咱初學者來說就先了解一個大概,最開始就要把這些原理弄得很清楚還是很費功夫的!如此,就找到了MFC程序入口及消息循環的基本原理了,至于MFC程序如何響應事件及另外的一些重要性質就讓我們日后挖掘吧。
評論