上一專題中,純手動地完成了一個Windows應用程序,然而,在實際開發中,我們大多數都是使用已有的類庫來開發Windows應用程序。MFC(Microsoft Foundation Class, 微軟基礎類庫)是微軟為了簡化程序員的開發工作而將Windows API 封裝到C++類中,利用這些類,程序員可以有效地完成Windows平台下應用程序的開發。本專題將詳細剖析它。
用於幫助有效地開發Windows應用程序的類庫除了MFC外,還有其他開源類庫提供,比如說QT,只是QT不是微軟開發的罷了,為了更好地剖析MFC,下面讓我們用Visual Studio中的MFC模板和向導工具來創建一個基於MFC的單文檔(SDI)應用程序。
上面代碼中是_tWinMain函數啊,並不是我要的WinMain函數的,難道是找錯了嗎?對於這個疑問,答案也是否定的,我們沒有找錯,這裡_tWinMain是一個宏定義,按F12即可以看到它代表的是WinMain。宏定義源碼如下(存在於tchar.h頭文件中):
_tmain main _tWinMain WinMain
為了證明我們找到的WinMain正是我們需要找到的入口函數,我們可以在appmodul.cpp文件中_tWinMain函數中設置一個斷點,然後按下F5按鈕運行SDIMFC程序,我們發現,SDIMFC程序會在我們剛才設置的斷點處停下來,具體如下圖所示:
我們已經找到了WinMain函數在MFC中的實現了,但是並沒有弄明白,我們創建的MFC程序是如何調用appmodul.cpp中的_tWinMain函數的,即程序中的MFC類如何與WinMain函數聯系起來的呢?下面就讓我們看看CSDIMFCApp類(至於為什麼想到該類,因為其後綴為App,即應用程序,所以猜測程序在進入WinMain函數之前會先進入該類),在類視圖中雙擊該類將在VS中看到該類的定義,從類定義可以知道,CSDIMFCApp類繼承於CWinAppEx類,CWinAppEx類又繼承於CWinApp,為了證明在WinMain函數之前先執行了CSDIMFCApp類中代碼,我們在CSDIMFCApp類中的構造函數設置一個斷點,然後按F5再運行下該程序,將發現程序首先停在CSDIMFCApp類的構造函數處,然後進入到_tWinMain函數(該斷點是我們之前設置的斷點)。這裡又引起另外一個疑問了——為什麼程序會首先調用CSDIMFCApp的構造函數呢?既然構造函數被調用了,肯定定義了該類的一個對象,然後,我們可以發現在CSDIMFCApp類中,定義了一個CSDIMFCApp類型的全局對象theApp,存在於SDIMFC.cpp文件中,具體定義代碼如下:
CSDIMFCApp theApp; //
然後我們在這個全局對象處設置一個斷點,然後再按F5調試運行下該程序,你將發現程序執行的順序為:theApp全局對象—>CSDIMFCApp構造函數(調用派生類的構造函數之前會調用其父類的構造函數)—>_tWinMain函數。在MFC程序中,theApp對象是用來唯一標識應用程序實例的,每個MFC程序有且僅有一個應用程序對象(這裡為theApp對象)。
現在我們已經找到MFC中的WinMain函數了,根據前一專題的內容,接下來就是找到MFC應用程序中的窗口類和注冊窗口類的代碼,在上一專題中,窗口類和注冊都是在WinMain函數中定義的,下面讓我們看下MFC中WinMain函數都幫我們封裝了什麼,在MFC中的WinMain函數中只是簡單對AfxWinMain函數進行調用,下面讓我們看看AfxWinMain具體代碼:
_In_ LPTSTR lpCmdLine, ASSERT(hPrevInstance == nReturnCode = - CWinThread* pThread = CWinApp* pApp = (! (pApp != NULL && !pApp-> (!pThread-> (pThread->m_pMainWnd != TRACE(traceAppMsg, , pThread->m_pMainWnd-> nReturnCode = pThread-> nReturnCode = pThread-> (AfxGetModuleThreadState()->m_nTempMapLock != TRACE(traceAppMsg, , AfxGetModuleThreadState()-> AfxUnlockTempMaps(- }
上面代碼首先調用AfxGetThread函數獲得一個指向CWinThread類型的指針,然後再調用了AfxGetApp函數獲得一個指向CWinApp類型的指針,再繼續調用AfxWinInit函數進行AFX(以AFX為前綴的函數為應用程序框架函數,Application Framework)內部初始化,接著pApp調用InitApplication函數,該函數主要完成MFC內部管理方面的工作,該函數為虛函數,在CWinApp中的實現為(函數實現代碼查找按照前面介紹的方式進行查看):
View Code接著繼續調用pThread的InitInstance函數,按F12可知,該函數聲明為虛函數,根據類的多態性,這裡AfxWinMain函數中調用的InitInstance函數為調用子類CSDIMFCApp的InitInstance函數,該函數的定義代碼為:
/ InitCtrls.dwSize = InitCtrls.dwICC = InitCommonControlsEx(& (! SetRegistryKey(_T( LoadStdProfileSettings(); ttParams.m_bVislManagerTheme = theApp.GetTooltipManager()-> RUNTIME_CLASS(CMFCToolTipCtrl), & CSingleDocTemplate* pDocTemplate = RUNTIME_CLASS(CMainFrame), (! (! m_pMainWnd-> m_pMainWnd-> }
在上面代碼中,,m_pMainWnd為我們創建的窗口,按F12轉到其定義為指向CWnd類型的指針,Cwnd類是MFC為我們預定義的標准窗口類,現在我們已經找到MFC程序中的窗口類,接下來就是找到MFC中是如何注冊窗口的,在MFC中,窗口類的注冊是由AfxEndDeferRegisterClass函數完成的,其定義在Wincore.cpp文件中,AfxEndDeferRegisterClass函數內部又是通過AfxRegisterClass函數(該函數也定義在wincore.cpp文件中)來注冊窗口類,由於篇幅的問題,這裡就不貼其函數的定義源碼,大家可以在本機中進行查看。
我們已經找到了MFC設計窗口和注冊的封裝,接下來就是MFC程序中是如何創建一個窗口的,該功能在MFC中是由CWnd類的CreateEx函數進行完成的,該函數的聲明在afxwin.h文件中,具體代碼如下:
x, y, nWidth, = NULL);
實現代碼位於wincore.cpp文件中,我們程序中創建的是CMainFrame窗口,CMainFrame類繼承於CFrameWndEx,該類又繼承於CFrameWnd,CFrameWnd類的Create函數內部會調用CreateEx函數,而CFrameWnd的Create函數又由CFrameWnd類的LoadFrame函數調用。CFrameWnd類的Create函數聲明位於afxwin.h文件中,其實現代碼位於winfrm.cpp文件中,實現代碼如下:
View Code在CSDIMFCApp類的InitInstance函數內容即有窗口顯示和更新窗口的代碼,具體代碼如下:
m_pMainWnd->->UpdateWindow();
CWinThread類的Run函數就是完成消息循環這一任務的,該函數在AfxWinMain函數中進行了調用,其定義在thrdcore.cpp文件中,其定義代碼如下所示:
View Code在AfxEndDeferRegisterClass函數中其中有一行這樣的代碼(下面代碼紅色標記處):
AFX_MODULE_STATE* pModuleState =&= ~pModuleState-> (fToRegister == = &wndcls, , (WNDCLASS)); wndcls.hInstance == afxData.hcurArrow;
.....
}
但實際上,MFC中並不是把所有消息都交給DefWindowProc這一默認窗口過程進行處理的,而是采用了一種稱為來處理各種消息,MFC消息映射機制指的是可以通過類向導為類添加消息處理函數,具體操作為,在類視圖中右鍵某個類,然後選擇類向導,在彈出的MFC類向導窗體中切換到消息選項卡來添加某個消息的處理函數,下圖是CMainFrame執行類向導的截圖:
該過程類似.NET中WinForm中通過某個控件的事件來添加事件處理函數。(WinForm中事件對應於MFC中的消息)。
至此,我們已經分析完了MFC程序的運行機制了,可以發現其經歷過程和前一專題介紹是一致,只是MFC中我們不需要自己實現這些過程了,這些都由MFC框架幫我們封裝好了,從而減少開發人員的任務量,將更多的時間放在實現程序的業務邏輯上面。下面讓我們一起來梳理下MFC程序的運行過程:
我們創建的MFC程序除了主框架窗口外,還有一個窗口是視類窗口,對應於CView類,框架窗口是視類窗口的一個父窗口,它們之間的關系如下圖所示。主框架窗口是整個應用程序外框所包括的部分,而視類窗口只是主框架窗口中的空白的地方。
在我們之前創建的MFC程序中還有一個CSDIMFCDoc類,它派生與CDocument類,後者的基類又是CCmdTarget,而CCmdTarget又派生於CObject類,從而,可以知道CSDIMFCDoc類不是一個窗口類,實際上它是一個文檔類。MFC提供了一個文檔/視圖結構(Document/View),這裡文檔指的是CDocument類,而視圖指的是CView類。微軟在設計MFC時,考慮到數據本身應該與它的顯示分離(這點在微軟的很多技術中都有體現,例如Asp.net MVC ),於是就采用文檔和視圖結構來實現這一想法。數據的存儲和加載由文檔類來完成,數據的顯示和修改由視圖類來完成,從而把數據管理和顯示方法分離開來。
到此,本專題的內容就介紹結束了,本專題主要剖析了MFC框架的運行機制,從而發現MFC應用程序同樣遵循Win32 SDK程序相應的過程,包括設計窗口類、注冊窗口類、創建窗口、顯示和更新窗口、消息循環和窗口處理過程函數,只不過這些操作都被MFC本身封裝好了。