热门问题
时间线
聊天
视角
MFC (微軟)
来自维基百科,自由的百科全书
Remove ads
微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是一個微軟公司提供的類庫(class libraries),以C++類的形式封裝了Windows API,並且包含一個(也是微軟產品的唯一一個)應用程序框架,以減少應用程序開發人員的工作量。其中包含的類包含大量Windows句柄封裝類和很多Windows的內建控件和組件的封裝類。
特性
Visual C++包含MFC應用程序嚮導,可用於兼容MFC的應用程序[1]。在ATL程序中也可以手動添加MFC支持[2]。在嚮導中有各種選項以定製生成的程序的功能,例如界面風格、語種、數據庫開發支持、打印支持、自動化支持、ActiveX支持、網絡支持、基於HTML的幫助文檔支持等等。
在COM開發方面,相對於ATL來說,MFC的組件比較大,代碼不夠短小精悍,但是支持的功能也比較多,例如有對ActiveX Document的封裝類[3]。
在界面開發方面,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)。[4]
隨着.NET框架的發布,曾經一度被微軟重點推薦的MFC被Visual Basic .NET、C#、Windows Forms搶走了不少市場份額,但是MFC繼續在非託管軟件開發中占據重要地位。在託管開發方面,MFC中也包括對Windows Forms和託管/非託管互操作的封裝。微軟在Windows Vista和Windows 7發布之後在MFC中增加了對新的Windows API支持[5][6]。
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。
Remove ads
MFC的結構
作為一個應用程序的開發框架,必須滿足各方面的功能需求。
基於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函數,對於WM_PAINT、WM_SYSTIMER、以及光標位置沒有變化的WM_MOUSEMOVE或WM_NCMOUSEMOVE,為Idle Message。
- 各個窗口函數(WndProc)內部首先獲取對應當前窗口句柄的CWnd類的指針,然後調用AfxCallWndProc函數。
Remove ads
- 如果是點擊了IDOK按鈕,默認是調用OnOK(),然後是OnDestory(),最後是PostNcDestroy()
- 如果點擊IDCANCEL按鈕,默認調用OnCancel(),然後是OnDestory(),最後是PostNcDestroy()
- 如果點擊右上角的關閉按鈕:先OnClose(),然後是OnCancel(),再然後是OnDestory() ,最後是PostNcDestroy()

Remove ads
窗體上的控件,應當向父窗體通報控件發生的各種事件,如被點擊、繪製、內容改變等等,稱為通知消息(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數據結構,其後是與該通知類型相關的特定數據結構。
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),[19]允許控件通知消息既可以在父窗口中,也可以在控件中被處理。可以對控件創建一個派生的控件類,實現對從父窗口反射回來的指定類型消息的處理。消息反射是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數組中。
struct AFX_MSGMAP_ENTRY
{
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::*AFX_PMSG)(void)的成員函數指針強制轉為其它類型的CCmdTarget成員函數指針(例如void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int),目前在union MessageMapFunctions中列出了近百種CCmdTarget成員函數指針),然後調用轉換後的成員函數指針。這是基於Visual C++編譯器把單繼承的成員函數指針編譯為只保存了函數的內存起始地址,因此可以在同一個單繼承類中把一種類型的成員函數指針強制轉換為另一種成員函數指針,或者把單繼承派生類的成員函數指針強制轉換為基類成員函數指針。這是打破了C++標準的違例辦法。例如,對於CWnd::OnCommand函數,轉換過程是:
BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()
CString
CString是MFC中最常見的類之一,用於封裝字符串數據結構。它只有一個數據成員m_pszData,其值為字符串的首地址,其數據類型為wchar_t*或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字節)。[20]
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的每個輸出的函數的最開始之處調用
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
[21]。函數AfxGetStaticModuleState的功能是在運行棧上創建一個AFX_MODULE_STATE類的實例,對其進行設置,函數返回值為AFX_MODULE_STATE的指針。AFX_MODULE_STATE類利用其構造函數和析構函數進行模塊狀態現場存儲及恢復。 - 使用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都有默認值。它們定義於此文件中。
參考和引用
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads