【筆記】《深入淺出MFC》第6章 MFC程式的生死因果

QingLiXueShi發表於2015-11-01

一、標頭檔案說明

STDAFX.H

這個檔案用來作為Precompile header file,其內只是載入其他的MFC標頭檔案。應用程式通常會準備自己的頭STDAFX.H。

AFXWIN.H

每一個Windows MFC程式都必須載入它,因為它以及它所載入的檔案宣告瞭所有的MFC類。

在WINDEF.H中有CALLBACK的定義

#define CALLBACK _stdcall     //是一種函式呼叫習慣

在AFXWIN.H中有afx_msg的定義

#define afx_msg        //故意安排一個空位置,也許以後版本會用到。

所有MFC標頭檔案均置於\MSVC\MFC\INCLUDE中。這些檔案在編譯過程中耗費大量時間,所以有必要設定Precompiled header。一個應用程式在常需要不斷地編譯。Windows程式載入的.H檔案非常巨大但內容不變,編譯器浪費在上面的時間非常多。Precompiled header就是將.H檔案第一次編譯後的結果儲存起來,第二次再編譯時就可以直接從磁碟中取出來用。

二、MFC程式的來龍去脈

CWinApp代表程式本體。CFrameWnd代表一個主框架視窗。必須以這兩個類為基礎,派生自己的類,並改寫其中一部分成員函式。

全域性物件theAPP,就是application object。每一個應用程式都應該改寫CWinApp::InitInstance()函式。

MFC把有著相當固定行為的WinMain內部操作封裝在CWinApp中,把有著相當固定行為的WinProc內部操作封裝在CFrameWnd中。

傳統SDK程式WinMain完成的工作,現在由CWinApp的三個函式完成。

Virtual BOOL InitApplication();

Virtual BOOL InitInstance();

Virtual int Run();

CWinApp繼承CWinThread了成員變數m_pMainWnd,代表主視窗。CFrameWnd主要用來掌握一個視窗,它是用來取代SDK程式中的視窗函式的地位。

 

我們並未寫WinMain程式程式碼,這是MFC早已準備好並由連結器直接加到應用程式程式碼中的。_tWinMain函式的_t是為了支援UniCode而準備的一個巨集。

下面是AfxWinMain程式碼。

將以上程式碼整理一下就得到下面這段程式碼。

AfxGetApp 是一個全域性函式,它取得CMyWinApp 物件指標。AfxWinInit 是繼CWinApp 構造式之後的第一個動作。AfxWinInit 之後的動作是pApp->InitApplication。

以上程式碼這些動作都是MFC 為了內部管理而做的。繼InitApplication 之後, AfxWinMain 呼叫pApp->InitInstance。

一般而言,CMyWinApp只改寫CWinApp中的InitInstance,通常它不改寫InitApplication和Run。

注意:應用程式一定要改寫虛擬函式InitInstance,因為它在CWinApp 中只是個空函式,沒有任何內建(預設)動作。

CMyWinApp::InitInstance 一開始new 了一個CMyFrameWnd 物件,準備用作主框視窗的C++ 物件。CFrameWnd::Create 在產生視窗之前,會先引發視窗類別的註冊動作。

 

下面是CreateEx程式碼。

呼叫的PreCreateWindow 是虛擬函式, CWnd 和CFrameWnd 之中都有定義。由於this 指標所指物件的緣故,這裡應該呼叫的是CFrameWnd::PreCreateWindow。

利用AfxDeferRegisterClass巨集註冊視窗類。不同類別的PreCreateWindow 成員函式都是在視窗產生之前一刻被呼叫,準備用來註冊視窗類別。

 

CMyFrameWnd::CMyFrameWnd 結束後, 視窗已經誕生出來;程式流程又回到CMyWinApp::InitInstance ,於是呼叫ShowWindow 函式令視窗顯示出來,並呼叫UpdateWindow 函式令Hello 程式送出WM_PAINT 訊息。

視窗類別註冊好了,視窗誕生並顯示出來了, UpdateWindow 被呼叫,使得訊息佇列中出現了一個WM_PAINT 訊息,等待被處理。現在,執行pApp->Run。

 

Message Map 機制是為了提供更方便的程式介面(例如巨集或表格),讓程式設計師很方便就可以建立起訊息與處理例程的對應關係。

MFC 提供給應用程式使用的「很方便的介面」是兩組巨集。以Hello 的主視窗為例,第一個動作是在HELLO.H 的CMyFrameWnd 加上DECLARE_MESSAGE_MAP:

第二個動作是在HELLO.CPP 的任何位置(當然不能在函式之內)使用巨集如下:

MFC 把訊息主要分為三大類, Message Map 機制中對於訊息與函式間的對映關係也明定以下三種:

1、標準Windows 訊息(WM_xxx)的對映規則:

2、命令訊息( WM_COMMAND)的一般性對映規則是:ON_COMMAND(<id>,<memberFxn>)

例如:

ON_COMMAND(IDM_ABOUT, OnAbout)

ON_COMMAND(IDM_FILENEW, OnFileNew)

ON_COMMAND(IDM_FILEOPEN, OnFileOpen)

ON_COMMAND(IDM_FILESAVE, OnFileSave)

3、Notification 訊息(由控制元件產生,例如BN_xxx)的對映機制的巨集分為好幾種(因為控制元件本就分為好幾種),以下各舉一例做代表:

各個訊息處理函式均應以afx_msg void 為函式型別。如果某個訊息在Message Map 中找不到對映記錄,它會往基礎類別流竄,這個訊息流竄動作稱為Message Routing。如果一直竄到最基礎的類別仍找不到對映的處理例程,由預設函式來處理。

 

【總結】

 

凡是由你設計而卻由Windows 系統呼叫的函式,統稱為callback 函式。這些函式都有一定的型別,以配合Windows的呼叫動作。

callback 函式是給Windows 呼叫的, Windows 並不經由任何物件呼叫這個函式,也就沒有傳遞this 指標給callback 函式,於是導至堆疊中有一個隨機變數會成為this 指標,而其結果當然是程式的崩潰了。

要把某個函式用作callback 函式,兩個方法可以做到這一點:

(1)不要使用類的成員函式(也就是說,要使用全域性函式)做為callback 函式。

(2)使用static 成員函式。也就是在函式前面加上static 修飾詞。

相關文章