1. 程式人生 > >VC++深入詳解(1):MFC框架程式剖析

VC++深入詳解(1):MFC框架程式剖析

學了一段Win32SDK應用程式以後,因為種種雜七雜八的事情,讓windows程式設計的內容停滯了很長一段時間。但是我今天還是鼓足了勇氣,繼續開始後面的內容。(不過後面的筆記不再是跟著楊力祥老師的上課內容了,因為他對MFC的講解似乎課程剩下的不是很足,所以我換了孫鑫老師的聽)。
咱們直接從第三節課講起吧。第一節課講的是用Win32SDK應用程式寫“hello world”,我們之前已經做過很多遍了,這裡只簡要的回顧一下整個程式的脈絡:
WinMain函式是整個程式的入口,相當於C語言中的main函式,在WinMain中,我們完成了一下幾個重要的事情:設計視窗類,註冊視窗類,建立視窗,顯示視窗,更新視窗,進入訊息迴圈。在設定視窗類中,指明訊息處理函式,並把這個重要的資訊在註冊視窗時告訴作業系統,而在訊息迴圈,每當我的應用程式收到了訊息,都會把這個訊息投遞到這個應用程式的訊息佇列中,然後程式依次從中取走訊息,並把訊息告訴作業系統,作業系統呼叫訊息處理函式來響應這些訊息。
首先,個人覺得windows應用程式跟dos下的控制檯應用程式最大的區別有兩點。第一點是表象的:控制檯應用程式是“黑屏的”,而windows應用程式是基於“視窗的”;第二點是內在的,控制檯應用程式的核心內容是與作業系統無關的(雖然我們總會頻繁的使用printf是得能從螢幕上顯示列印結果);而windows應用程式是跟作業系統密切相關的。
第二講主要是複習C++裡面的一些基本知識:類、繼承、派生、多型、過載等等。這裡就不提了。
下面我們言歸正傳,開始MFC的學習。MFC是微軟基礎類庫,它是對我們前面使用的windowsAPI函式,使用面向物件的方法進行了封裝,它大大的簡化了應用程式的開發過程。(順便吐槽幾句,這個封裝其實做的並不是非常出色,使用了大量的巨集,而C++程式設計師是非常討厭巨集的,但是總而言之,用起來還不錯,尤其是使用AppWizard開發嚮導以後)。
這節課的主要目的,就是在於向讀者展示:儘管MFC對windowsAPI進行了封裝,但是它的程式在執行的過程中,總是要遵循API裡面設計視窗類,註冊視窗類,建立視窗,顯示視窗,更新視窗,進入訊息迴圈的步驟。
首先,我們新建一個單文件的MFC應用程式,對於其他內容保持預設設定,然後我們不寫一行程式碼,直接編譯程式,然後執行,就能看到一個像模像樣的文件程式了。這當然是AppWizard的功勞,它默默的為你寫了5個類:假設的工程名為CH_3_TEST,那麼這5個類分別是:CAboutDlg、CCH_3TESTApp、CCH_3TESTDoc、CCH_3TESTView和CMainFrame,還有一個全域性變數theApp。注意在編寫MFC的程式時,我們更多的使用“類檢視”而不是檔案檢視。
在這麼多的程式碼中,你無法搜尋到Win32SDK應用程式下的WinMain,更找不到我們熟悉的RegisterClass,CreateWindow,ShowWindow,UpdateWindow,以及GetMessage等等。那麼寫程式碼的入口在哪裡,又是如何執行的呢?
找不到的原因在於,MFC把它們都封裝了起來。它們對應的原始檔在VC++6.0下面的安裝目錄:\Microsoft Visual Studio\VC98\MFC\SRC下面,我們粗粗把這些函式一一還原。
在APPMODUL.CPP下面找到:_tWinMain,而_tWinMain是一個巨集,它其實就是WinMain。如果不信,可以在_tWinMain處設一個斷點,我們就會發現,除錯執行時程式會執行到斷點處。
那麼下面的問題是?這5個類是如何與WinMain關聯到一起的?答案是通過全域性變數theApp。我們可以在CCH_3TESTApp的建構函式處設一個斷點,我們會發現整個程式會先執行建構函式,在執行WinMain。這隻有一種可能,就是因為整個程式中有一個CCH_3TESTApp類的全域性物件,也就是theApp。這也可以通過加上斷點加以驗證。我們記得,對於Win32SDK應用程式,是通過一個控制代碼hInstance來代表整個程式的,而對於MFC,則是通過這個全域性物件代表的。對於這個全域性物件,由於構造它的時候需要呼叫基類的建構函式,我們就把自己寫的程式跟MFC庫關聯的起來:CCH_3_TESTApp繼承自CWinApp,它的建構函式定義在APPCORE.CPP中,其中有兩句:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this;
其中的this指標指向的是theApp。
至此,我們明白了下面3件事情:我們定義了全域性物件theApp,而theApp需要使用它的基類的建構函式,當構造完theApp後,進入WinMain函式。
而WinMain函式的執行,是通過呼叫AfxWinMain函式來實現的,這個函式的定義在WINMAIN.CPP中。在MFC中,Afx開頭的函式都是全域性函式,它們在每個類中都可以方便的呼叫。裡面有兩句值得注意:
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
其中AfxGetThread是THRDCORE.CPP中定義的。而它是通過AfxGetApp實現的。而它是通過afxCurrentWinApp實現的。afxCurrentWinApp是一個巨集,實際上呼叫的是AfxGetModuleState()->m_pCurrentWinApp,而在CWinApp的建構函式中,我們已經把m_pCurrentWinApp這個變數賦值為this指標。兜了這麼大一個圈,這個其實這兩個函式獲得的是同一個指標,它們指向全域性物件。
接下來,AfxWinMain通過了3個函式來完成了設計視窗類,註冊視窗類,建立視窗,顯示視窗,更新視窗,進入訊息迴圈,以及視窗過程函式:InitApplication、InitInstance和
Run。
其中InitApplication做的是mfc內部管理所呼叫的函式,InitInstance是一個虛擬函式,它會呼叫派生類中的InitInstance。這個函式中ShowWindow和UpdateWindow我們似曾相識,它們呼叫的就是SDK下的對應的函式。但是註冊視窗、建立視窗的步驟是在哪裡完成的呢?這裡不管是原書還是視訊都沒有講清楚,呼叫過程,只說了結果:
AfxEndDeferRegisterClass(WINCORE.CPP中)完成的是註冊視窗的操作。它對於視窗設定了大量預設的樣式,這裡只需要判斷是樣式中的哪一種,選擇即可。然後呼叫AfxRegisterClass來完成註冊。而建立視窗的過程就更加複雜了,因為自己沒有嘗試跟蹤過,所以不做敘述了。希望等懂一些之後再補上。
而Run函式則完成的是訊息迴圈工作:先是一個for迴圈,再套了while迴圈,其中PumpMessage中調了GetMessage、TranslateMessage和DispatchMessage這個三個函式。


大體的脈絡就是這樣的,可見雖然MFC乍看上去跟API毫無關係,但是追本溯源,它只是對API的封裝,最後,還是呼叫API函式實現的,windows並沒有提供另外的一套庫來專門供C++程式設計師使用。
那麼使用MFC的方便之處在哪裡呢?我們可以舉一個例子:給文字框程式新增一個按鈕。
首先,按鈕肯定是在接受到WM_CREAT訊息時新增上去的,所以應該在響應WM_CREAT訊息的函式OnCreate下新增一個一個CButton物件,然後呼叫它的Create函式來初始化它。但仔細一想這樣做是有問題的,如果定義了一個區域性物件,那麼當OnCreate呼叫完成之後就會發生析構,視窗就消失了,所以應該把按鈕設定為成員變數。
還有一點值得注意,就是如果是在CMainFrame的OnCreate下建立按鈕,如果使用預設的位置,則會覆蓋到原來的選單。我們有兩種思路解決這個問題:1.修改建立的位置。2.在view類裡面建立。但是view裡面並沒有OnCreate函式,怎麼辦呢,這時我們的AppWizard就派生用場了:對這類新增一個CButton成員,在類名上單擊右鍵,選擇Add windows message Handler...然後選擇訊息,並“填空”完成訊息響應函式就行了。這裡就不再羅嗦了。
從這裡我們也可以看出,view類的範圍是視窗的顯示部分,而frame類要更大一些,包含了選單等內容。