热门问题
时间线
聊天
视角
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