MFC_(微軟)

**微軟基礎類別館**（英語：**M**icrosoft **F**oundation **C**lasses，簡稱 **MFC**）是一個微軟公司提供的類別館（class libraries），以 C++ 類的形式封裝了 Windows API，並且包含一個（也是微軟產品的唯一一個）應用程式框架，以減少應用程式開發人員的工作量。其中包含的類包含大量 Windows 控制代碼封裝類和很多 Windows 的內建控制項和組件的封裝類。

## 特性

Visual C++ 包含 MFC 應用程式精靈，可用於相容 MFC 的應用程式。在 ATL 程式中也可以手動添加 MFC 支援。在精靈中有各種選項以客製化生成的程式的功能，例如介面風格、語種、資料庫開發支援、列印支援、自動化支援、ActiveX 支援、網路支援、基於 HTML 的說明文件支援等等。

在 COM 開發方面，相對於 ATL 來說，MFC 的組件比較大，代碼不夠短小精悍，但是支援的功能也比較多，例如有對 ActiveX Document 的封裝類。

在介面開發方面，MFC 提供對訊息迴圈的封裝，使用訊息對映來避免虛擬函式的開銷。MFC 也提供常用 Windows 通用控制項的封裝類。

MFC 擴充 DLL 的介面使得 MFC 程式可以直接呼叫 MFC 擴充 DLL 中的 MFC 類。MFC 也支援在標準 DLL 中被使用。

## 發展

MFC 是在 1992 年隨微軟的 Microsoft C/C++ 7.0 編譯器發布的，用於面向 16 位元 Windows 的軟體開發。起初，MFC 是作為一個應用程式框架開發的，所以定名為 Application Framework eXtensions（AFX）。

隨著.NET 框架的發布，曾經一度被微軟重點推薦的 MFC 被 Visual Basic .NET、C#、Windows Forms 搶走了不少市場份額，但是 MFC 繼續在非代管軟體開發中占據重要地位。在代管開發方面，MFC 中也包括對 Windows Forms 和代管／非代管互操作的封裝。微軟在 Windows Vista 和 Windows 7 發布之後在 MFC 中增加了對新的 Windows API 支援。

## MFC 的優點

MFC 的主要優點是可以用物件導向的方法來呼叫 Windows API，以及能夠更加便捷地開發應用程式。MFC 將很多應用程式開發中常用的功能自動化，並且提供了文件框架視圖結構和活動文件這樣的便於自訂的應用程式框架。同時，在 Visual C++ 內部也內建了很多對 MFC 的例如類精靈這樣的支援以減少軟體開發的時間，使用類精靈可以快速生成 Hello World 程式。

## MFC 的缺點

雖然 MFC 的原始碼對使用者是完全開放的，但是 MFC 的一些封裝過程過於複雜，以致於新使用者很難迅速掌握 MFC 的應用程式框架，以及在除錯中定位問題的位置。同時，很多 MFC 物件不是執行緒安全的，致使在跨執行緒存取 MFC 物件時需要編寫額外的代碼。另外，MFC 的很多類依賴於應用程式精靈生成的代碼，使得在使用 Visual C++ 中其他類型的應用程式精靈生成的工程中添加 MFC 支援的難度大大增加。

## 第三方支援

很多商用類別館在 MFC 的基礎上進一步實現了外觀、漸變風格、多頂層窗口程式、屬性列表等較受歡迎的功能；同時，在 C++ 線上社群中，很大一部分開放的原始碼也是基於 MFC 的。

 

## 版本

 

* 1 Visual Studio 速成版（Express）不包含 MFC 程式庫。
* 2 Feature Pack 只用於英文版本的 Visual Studio 2008。非英文版本的支援於將包含於 Visual Studio 2008 Service Pack 1。

## MFC 的結構

作為一個應用程式的開發框架，必須滿足各方面的功能需求。

### 應用程式啟動

基於 MFC 開發的應用程式在啟動時，Windows 作業系統：

1. 首先呼叫 WinMain 函式（位於 appmodul.cpp 中，封裝到 mfc80.dll（VS2005 版）），WinMain 函式內呼叫了 AfxWinMain 函式。
2. AfxWinMain 函式（位於 WinMain.cpp 中）呼叫了
  1. 該應用程式自訂的 App 類（這個類衍生於 CWinApp 的，CWinApp 又是衍生於 CWinThread，因此代表了應用程式的主執行緒）的 InitInstance 函式，該函式註冊並建立窗口（通過 AppUI2.cpp 中的 ProcessShellCommmand 函式)，然後 ShowWindow、UpdateWindow;
  2. CWinThread 的 InitInstance 函式；
  3. CWinThread 的 Run 函式（位於 thrdcore.cpp 中）。該函式內部是 Windows 的訊息迴圈。 當應用程式收到 WM\_QUIT 訊息後，CWinThread::Run 函式返回，緊接著 CWinThread::ExitInstance 被呼叫，該函式可被覆蓋。程式至此登出執行。 訊息迴圈是一個 for (;;) 的無窮迴圈，該無窮迴圈內部包含了一個 do...while 的迴圈結構。while 迴圈條件是呼叫 PeekMessage 函式的返回值，如果當前 UI 執行緒訊息佇列為空就返回到外層的無窮迴圈；while 迴圈體內做兩件事：
    1. PumpMessage ()。實際呼叫 AfxInternalPumpMessage 函式實現其功能：GetMessage ()、AfxPreTranslateMessage ()、TranslateMessage ()、DispatchMessage (). 即：從 UI 執行緒訊息佇列移除一條訊息、遍歷該訊息的 CWnd 類直到該窗口的各級別父窗口的 CWnd 類以提供預處理該訊息的機會、如果該訊息是按鍵訊息則翻譯為 WM\_CHAR 訊息、把該訊息給相應的窗口函式。
    2. IsIdleMessage ()：實際呼叫了 AfxInternalIsIdleMessage 函式，對於 WM\_PAINT、WM\_SYSTIMER、以及游標位置沒有變化的 WM\_MOUSEMOVE 或 WM\_NCMOUSEMOVE，為 Idle Message。
  4. 各個窗口函式（WndProc）內部首先取得對應當前窗口控制代碼的 CWnd 類的指標，然後呼叫 AfxCallWndProc 函式。

### 應用程式結束

* 如果是點擊了 IDOK 按鈕，預設是呼叫 OnOK ()，然後是 OnDestory ()，最後是 PostNcDestroy ()
* 如果點擊 IDCANCEL 按鈕，預設呼叫 OnCancel ()，然後是 OnDestory ()，最後是 PostNcDestroy ()
* 如果點擊右上角的關閉按鈕：先 OnClose ()，然後是 OnCancel ()，再然後是 OnDestory () ，最後是 PostNcDestroy ()

### 訊息迴圈與訊息對映

#### 訊息分類

#### 控制項的通知訊息與反射訊息

表單上的控制項，應當向父表單通報控制項發生的各種事件，如被點擊、繪製、內容改變等等，稱為**通知訊息**（notification message ）。在 Windows 3.x 的 16 位元程式設計時代，控制項向父表單傳送 WM\_COMMAND 訊息，由父表單的代碼負責實現這些事件。其中 wParam 的低 16 位元是 control ID，高 16 位元是 notification code (例如 BN\_CLICKED)；lParam 是控制項控制代碼。因此，再無可能傳遞其它資訊給父表單。為此，為傳遞具有特別內容的控制項事件，Windows 3.x 定義了一批特殊的通知訊息 (notification messages)：

* WM\_PARENTNOTIFY: 子窗口的某些重大事件發生時通知父窗口，包括建立、銷毀、滑鼠各鍵按下等事件
* 子窗口的捲動情況的通知訊息
  * WM\_VSCROLL
  * WM\_HSCROLL
* 子窗口的繪製通知訊息
  * WM\_DRAWITEM,
  * WM\_MEASUREITEM,
  * WM\_COMPAREITEM,
  * WM\_DELETEITEM,
  * WM\_CHARTOITEM,
  * WM\_VKEYTOITEM
  * WM\_CTLCOLOR：設定按鈕、編輯框、ListBox、Static、捲軸控制項與 MessageBox、DialogBox 的前景色、背景色、背景模式、字型，並返回一把 Brush，用於控制項背景繪製。

早於 4.0 版本的 MFC，在控制項類提供了虛擬函式處理這些通知訊息，這一辦法已經被下述的「訊息反射」取代（但仍然向下相容繼續支援）。

隨著 Windows 95 開始了 32 位元程式時代，伴之而來的是 Win32 API 與 MFC 4.0。 Win32 增加了很多複雜的控制項，需要使用更多的通知訊息傳遞很多複雜的資料給父表單。Win32 API 僅僅增加了一個訊息 WM\_NOTIFY，就實現了這些功能。lParam 參數開頭是 NMHDR 資料結構，其後是與該通知類型相關的特定資料結構。

CWnd::OnNotify 函式處理通知訊息。它的預設實現是檢查訊息對映表（message map）尋找通知的處理器函式並呼叫。一般說來，不必覆蓋 OnNotify；而應該寫一個處理器函式並增加為該窗口類的訊息對映表條目。

```
 ON_NOTIFY(wNotifyCode, id, memberFxn)
```

成員函式應該寫為：

```
 afx_msg void memberFxn(NMHDR* pNotifyStruct, LRESULT* result);
```

MFC 4.0 提供了一種特性「**訊息反射**」（message reflection），允許控制項通知訊息既可以在父窗口中，也可以在控制項中被處理。可以對控制項建立一個衍生的控制項類，實現對從父窗口反射回來的指定類型訊息的處理。訊息反射是 MFC 而不是 Win32 的特性，因此父窗口的類必須是從 CWnd 衍生，從而父窗口在 CWnd::OnNotify 函式中處理控制項的 WM\_NOTIFY 時，首先呼叫 CWnd::ReflectLastMsg 把訊息反射回控制項的 CWnd::SendChildNotifyLastMsg 函式去處理；ReflectLastMsg 返回值就是在控制項的訊息對映中使用 ON\_NOTIFY\_REFLECT\_EX () 聲明的反射訊息處理常式的返回值，可以通知父窗口該訊息是否已經被控制項處理。控制項的 CWnd::SendChildNotifyLastMsg 函式，首先獲得執行緒的最後一條 message，然後呼叫傳送窗口的虛擬函式 OnChildNotify 函式。在子窗口處理反射回來的控制項訊息，第一種方法是多載控制項窗口的 OnChildNotify 虛擬函式；第二種辦法是由 CWnd::OnChildNotify 預設處理去呼叫 CWnd::ReflectChildNotify 函式，進入控制項子窗口的 MFC 訊息對映的標準處理（子窗口處理的訊息被譯成 WM\_REFLECT\_BASE+WM\_NOTIFY 訊息）。對於 WM\_NOTIFY，僅當在控制項的訊息對映（message map）中，控制項沒有通過 ON\_NOTIFY\_REFLECT () 聲明的反射訊息處理常式，父窗口的相應的通知訊息處理常式才會被呼叫（在父窗口訊息對映中使用宏 ON\_NOTIFY 聲明）。控制項中通過 ON\_NOTIFY\_REFLECT\_EX () 聲明的反射訊息處理常式可以返回真或假，以決定父窗口是否繼續處理該通知訊息。WM\_NOTIFY 以外的其它通知訊息，父窗口在第一時間有機會處理它，控制項對它的處理排在第二位。反射訊息處理常式通常使用特定的名字，對應的訊息反射宏的名字是在訊息名字加上字首 ON\_，字尾 \_REFLECT。如 WM\_CTLCOLOR 對應 ON\_WM\_CTLCOLOR\_REFLECT。但以下三種情況，反射訊息處理常式可以隨意自行起名，對應的訊息反射宏的名字分別為：

* WM\_COMMAND 使用 ON\_CONTROL\_REFLECT
* WM\_NOTIFY 使用 ON\_NOTIFY\_REFLECT
* ON\_UPDATE\_COMMAND\_UI 使用 ON\_UPDATE\_COMMAND\_UI\_REFLECT

#### 訊息傳遞處理機制

MFC 類體系中，Windows 訊息傳遞處理機制是基於 CCmdTarget 類及其衍生類別的靜態成員函式 GetThisMessageMap () 內部定義的靜態資料成員：

* 成員類型為 AFX\_MSGMAP\_ENTRY 的陣列 \_messageEntries。在類的實現檔案中，在 BEGIN\_MESSAGE\_MAP 與 END\_MESSAGE\_MAP 之間的內容來初始化訊息對映入口項陣列。
* 資料類型為 AFX\_MSGMAP 的變數 messageMap。該結構包含兩項，分別是直接基礎類別 GetThisMessageMap 函式指標與本類的 \_messageEntries 陣列首元素位址。

在標頭檔的類別定義中使用宏 DECLARE\_MESSAGE\_MAP () 來聲明靜態成員函式 GetThisMessageMap 與虛擬函式 GetMessageMap

使用者所寫的類的 Windows 訊息處理常式（例如 OnCommand）必須轉換為 CCmdTarget::\* 的成員函式指標類型 AFX\_PMSG，儲存在該類的 \_messageEntries 陣列中。

呼叫使用者類中該訊息處理常式時，根據該函式儲存在 \_messageEntries 中的 signature（一個無符號整型表示的函式的形參類型列表與返回值類型），把類型為 void (CCmdTarget::\*AFX\_PMSG)(void) 的成員函式指標強制轉為其它類型的 CCmdTarget 成員函式指標（例如 void (AFX\_MSG\_CALL CWnd::\*pfn\_v\_i\_i)(int, int)，目前在 union MessageMapFunctions 中列出了近百種 CCmdTarget 成員函式指標），然後呼叫轉換後的成員函式指標。這是基於 Visual C++ 編譯器把單繼承的成員函式指標編譯為只儲存了函式的記憶體起始位址，因此可以在同一個單繼承類中把一種類型的成員函式指標強制轉換為另一種成員函式指標，或者把單繼承衍生類別的成員函式指標強制轉換為基礎類別成員函式指標。這是打破了 C++ 標準的違例辦法。例如，對於 CWnd::OnCommand 函式，轉換過程是：

## CString

CString 是 MFC 中最常見的類之一，用於封裝字串資料結構。它只有一個資料成員 m\_pszData，其值為字串的首位址，其資料類型為 wchar\_t\* 或 char\*。在 CString 的 m\_pszData 的前面實際還分配了 CSringData 資料塊，包含了管理資料:

由下而上，CAtlStringMgr 提供記憶體管理，CStringData 提供共享管理，CString 提供字串操作。

CAtlStringMgr 的一個成員是 IAtlMemMgr 介面，這是策略模式，可以參照某個記憶體管理類。CAtlStringMgr 的另一個成員是 CNilStringData。

因此，每次為 CString 動態分配位址空間，實際分配長度為:`(nChars+1)*nCharSize+sizeof(CStringData)`。通過 Attach 操作，將這個 CStringData\* 與 CSimpleStringT::m\_pszData 執行了關聯。當執行 CString 的預設建構函式生成一個空字串時，實際上都是構造一個 CnilStringData 物件。CNilStringData 衍生自 CStringData，額外擁有一個 achNil 的陣列成員，這個陣列初始化為空字串。通過這個 achNil，保證了一個經過呼叫預設建構函式初始化的 CString，其指向的真正的字串是一個空字串。

部分編譯器對 std::string 放棄了寫時複製（Copy On Write）機制。但是，CString 一直採取這一機制。CSimpleStringT::Fork 函式就提供了這樣一個操作，具體分為下面幾步：

1. 根據傳入的一個長度分配一段新的空間；—— Allocate (nLength, ...)
2. 把舊資料拷貝到新的空間裡面；—— CopyChars (...)
3. 舊資料塊的參照技術減 1； —— pOldData->Release ()
4. 把 m\_pszData 和新的資料塊關聯起來。—— Attach (pNewData)

GetString 方法返回的是唯讀的字串位址；而 GetBuffer 方法返回的是**可寫**的字串位址（如果資料區是共享的，則寫時複製），如果修改了字串內容，這時需要呼叫 ReleaseBuffer 方法把新的字串長度修改到元資料中（並在尾部增加 2 個 0 位元組）。

CString 物件用作可變參數函式（如 printf）的實參時，由於無法通過形參類型確定呼叫哪個 CString 的類型轉換運算子函式，因此有必要顯式指明要轉換的類型。如果需要在函式的參數傳遞 CString，由於 CString 使用了參照計數，因此函式參數傳遞一個 CString 物件是可行的；不需要修改其內容時，推薦使用 const CString&。

## 支援 MFC 的 DLL 開發

使用 Visual C++ 可以開發 3 種 DLL:

* 不使用 MFC 的 DLL；
* 使用 MFC 的規則的 DLL：輸出的函式不涉及 MFC，因此可以被支援 / 不支援 MFC 的應用程式呼叫該 DLL
  * 動態連結到 MFC（Regular DLLs dynamically linked to MFC）。
  * 靜態連結到 MFC（Regular DLLs statically linked to MFC）
* 使用 MFC 的擴充 DLL（Extension DLLs），只能動態連結到 MFC：輸出的函式涉及 MFC，也可以輸出基於 MFC 的衍生類別。

由於 DLL 與呼叫它的應用程式都可以有自己的 MFC 全域資料與控制代碼對映（handle mapping），如果控制代碼值相同，則預設使用應用程式的對映到的資源。為了不互相干擾，允許 DLL 內部使用自己的資源，必須在 DLL 函式的入口處把資源模組控制代碼從預設的應用程式切換為該 DLL。辦法是：

1. 在該 DLL 的每個輸出的函式的最開始之處呼叫 `AFX_MANAGE_STATE(AfxGetStaticModuleState( ))`。函式 AfxGetStaticModuleState 的功能是在執行棧上建立一個 AFX\_MODULE\_STATE 類別的實例，對其進行設定，函式返回值為 AFX\_MODULE\_STATE 的指標。AFX\_MODULE\_STATE 類利用其建構函式和解構函式進行模組狀態現場儲存及恢復。
2. 使用 AfxGetResourceHandle (); 取得當前資源模組控制代碼。使用 AfxSetResourceHandle (HINSTANCE xxx); 設定程式要使用的資源模組控制代碼。

## 常用標頭檔與庫檔案

* STDAFX.H 載入其他 MFC 標頭檔。
* AFXWIN.H 它和它載入的檔案聲明了所有的 MFC 類。其內包含 AFX.H，後者包含了 AFXVER\_.H，後者又載入了 AFXV\_W32.H，後者又載入 WINDOWS.H。
* AFXEXT.H 使用工具列、狀態列的程式必須載入這個檔案
* AFXDLGS.H 通用對話方塊（Common dialog）的 MFC 程式需要載入此檔案。它內部包含 COMMDLG.H
* FXCMN.H 通用控制項（common control）的 MFC 程式需要載入此檔案。
* AFXCOLL.H 使用 MFC 提供的容器都需要載入此檔案。
* AFXDLLX.H 凡使用 MFC extension DLLs 需要載入此檔案。
* AFXRES.H MFC 程式的 RC 檔案必須載入此檔案。此檔案中對於標準資源的 ID 都有預設值。它們定義於此檔案中。

## 參考和參照

* ATL
* Microsoft Visual Studio
* Visual Studio .NET
* Object Windows Library