MFC (微軟)

微軟基礎類庫（Microsoft Foundation Classes，簡稱 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 的結構

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

7.1 應用程式啟動

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

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

7.2 應用程式結束

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

7.3 訊息迴圈與訊息對映

7.3.1 訊息分類

7.3.2 控制元件的通知訊息與反射訊息

窗體上的控制元件，應當向父窗體通報控制元件發生的各種事件，如被點選、繪製、內容改變等等，稱為通知訊息（notification message ）。在 Windows 3.x 的 16 位程式設計時代，控制元件向父窗體傳送 WMCOMMAND 訊息，由父窗體的程式碼負責實現這些事件。其中 wParam 的低 16 位是 control ID，高 16 位是 notification code (例如 BNCLICKED)；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 資料結構，其後是與該通知型別相關的特定資料結構。

typedef struct tagNMHDR {

HWND hwndFrom;

UINT idFrom;

UINT code;

} 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 函式中處理控制元件的 WMNOTIFY 時，首先呼叫 CWnd::ReflectLastMsg 把訊息反射回控制元件的 CWnd::SendChildNotifyLastMsg 函式去處理；ReflectLastMsg 返回值就是在控制元件的訊息對映中使用 ONNOTIFYREFLECTEX 宣告的反射訊息處理函式的返回值，可以通知父視窗該訊息是否已經被控制元件處理。控制元件的 CWnd::SendChildNotifyLastMsg 函式，首先獲得執行緒的最後一條 message，然後呼叫傳送視窗的虛擬函式 OnChildNotify 函式。在子視窗處理反射回來的控制元件訊息，第一種方法是過載控制元件視窗的 OnChildNotify 虛擬函式；第二種辦法是由 CWnd::OnChildNotify 預設處理去呼叫 CWnd::ReflectChildNotify 函式，進入控制元件子視窗的 MFC 訊息對映的標準處理（子視窗處理的訊息被譯成 WMREFLECTBASE+WMNOTIFY 訊息）。對於 WMNOTIFY，僅當在控制元件的訊息對映（message map）中，控制元件沒有透過 ONNOTIFYREFLECT 宣告的反射訊息處理函式，父視窗的相應的通知訊息處理函式才會被呼叫（在父視窗訊息對映中使用宏 ONNOTIFY 宣告）。控制元件中透過 ONNOTIFYREFLECTEX 宣告的反射訊息處理函式可以返回真或假，以決定父視窗是否繼續處理該通知訊息。WMNOTIFY 以外的其它通知訊息，父視窗在第一時間有機會處理它，控制元件對它的處理排在第二位。反射訊息處理函式通常使用特定的名字，對應的訊息反射宏的名字是在訊息名字加上字首 ON，字尾REFLECT。如 WMCTLCOLOR 對應 ONWMCTLCOLOR_REFLECT。但以下三種情況，反射訊息處理函式可以隨意自行起名，對應的訊息反射宏的名字分別為：

WMCOMMAND 使用 ONCONTROL_REFLECT
WMNOTIFY 使用 ONNOTIFY_REFLECT
ONUPDATECOMMANDUI 使用 ONUPDATECOMMANDUI_REFLECT

7.3.3 訊息傳遞處理機制

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

成員型別為 AFXMSGMAPENTRY 的陣列messageEntries。在類的實現檔案中，在 BEGINMESSAGEMAP 與 ENDMESSAGE_MAP 之間的內容來初始化訊息對映入口項陣列。
資料型別為 AFXMSGMAP 的變數 messageMap。該結構包含兩項，分別是直接基類 GetThisMessageMap 函式指標與本類的messageEntries 陣列首元素地址。

在標頭檔案的類定義中使用宏 DECLAREMESSAGEMAP 來宣告靜態成員函式 GetThisMessageMap 與虛擬函式 GetMessageMap

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

struct AFXMSGMAPENTRY

{

	UINT nMessage; //windows 訊息代號

	UINT nCode; // WM_NOTIFY 的控制程式碼

	UINT nID; // WM_COMMAND 下面的 ID 號，如果為其他的訊息，則這個數字為 0

	UINT nLastID; // 和前面的 ID 一起組成一個範圍，用於傳送一次訊息，處理執行多次

	UINT nSig; // 標誌訊息處理函式的型別

	AFX_PMSG pfn; // 函式呼叫指標

};

typedef void (CCmdTarget::*AFX_PMSG)(void);

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

BOOL (CWnd::)(WPARAM, LPARAM lParam) => void (CWnd::) => void (CCmdTarget::*)

CString

CString 是 MFC 中最常見的類之一，用於封裝字串資料結構。它只有一個數據成員 mpszData，其值為字串的首地址，其資料型別為 wchart 或 char。在 CString 的 m_pszData 的前面實際還分配了 CSringData 資料塊，包含了管理資料:

IAtlStringMgr* pStringMgr;

int nDataLength;

int nAllocLength;

int nRefs;

自底向上，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 函式就提供了這樣一個操作，具體分為下面幾步：

根據傳入的一個長度分配一段新的空間；—— Allocate (nLength, ...)
把舊資料複製到新的空間裡面；—— CopyChars (...)
舊資料塊的引用技術減 1； —— pOldData->Release
把 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。辦法是：

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

常用標頭檔案與庫檔案

STDAFX.H 載入其他 MFC 標頭檔案。
AFXWIN.H 它和它載入的檔案聲明瞭所有的 MFC 類。其內包含 AFX.H，後者包含了 AFXVER.H，後者又載入了 AFXVW32.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 都有預設值。它們定義於此檔案中。