作者:孫輝
MFC已經有十幾年的歷史了,然而直到今天,他仍然是Visual C++的關鍵組成部分。從1996年的Visual C++ 4.2至今將近8年的時間,MFC的主體特征沒有出現明顯的變化,依舊是“古老”的面孔,因此關於這個類庫的種種評論自然是情理之中的事情了。從我個人的觀點上看,MFC功能依舊健壯、強大,而且是業界少有的、穩定的、經過足夠長歷史考驗的開發框架。深入研究這個類庫,你會找到酒越釀越醇的感覺。MFC的一個成功因素之一就是提供了一套完整的Document/View框架,然而這一點也是許多針對MFC批評的矛頭指向。也許是由於這個框架太經典,使人們看上去MFC不再“婷婷玉立”,而是“人老珠黃”,以至於打開今天的Visual Studio IDE的時候,多少會有點不協調的感覺:比起其它基於.NET框架的開發語言,用MFC開發會顯得很“土氣”、“孤獨”——沒有RAD機制,明顯的缺乏與其它時髦對象的“互操作”能力,而且嚴格恪守自己的領地。每當生成一個基於文檔的MFC程序,我們總是看到一張滄桑的面孔,好像劉姥姥進入大觀園,與周圍時髦的C#、VB.NET等存在明顯的“代溝”與“不相容”。曾經有很多人問我MFC還有前途嗎?是否已經行將就木?關於是MFC還是.NET的討論時隱時現,不絕於耳。CLR是個充滿魅力的世界,這種魅力,使得C#、VB.NET等變得光彩奪目。然而,MFC並沒有衰老,如果你深入的了解MFC,你會發現,MFC完全可以與C#、VB.NET爭奇斗艷……在MFC項目中使用托管擴展
支持托管擴展
.NET FrameWork提供的托管擴展支持確保了在MFC項目支持托管擴展(CLR),開發者可以使MFC工程(本文我們將使用Test作為工程名稱)通過打開項目的托管擴展屬性開關,來增加編譯器的托管支持(如圖1)。
(圖1:打開托管編譯支持開關)
對偶.NET對象
在打開托管擴展編譯開關以後,您就可以在MFC項目中使用托管對象了,通常的做法是:為每個重要的MFC對象匹配一個托管對象以形成一個對偶對,彼此匹配的對象包含指向對方的指針,這樣,其他.NET對象可以通過對偶對中的.NET對象操作MFC對象;而其他MFC對象可以通過對偶對中的MFC對象操作.NET對象(如圖2)。
(圖2:對偶托管對象)
在Visual Studio .NET 中,沒有提供關於添加托管C++類對象的向導,因此,你可以先添加一個基於托管C++的Component對象(如圖3)。
(圖3. Add Class向導:增加托管C++ Component對象)
添加了該testDocObject托管組件對象之後,將該對象的基類改為Object,並刪除一些代碼得到一個最小托管類:
namespace test { __gc public class testDocObject : public Object { public: testDocObject(void) { } }; }
經過以上步驟,Visual Studio.NET生成的代碼被裝進了MFC程序,當然完全可以手動創建.h文件和.cpp文件,輸入相應的代碼,然後把它們添加到當前工程。由於以上步驟在托管擴展編程中經常遇到,因此,將上述過程自動化是必要的,有鑒於此,我們在附贈的光盤中提供了完整的添加.NET對象的Wizard。
在MFC非托管類中定義托管成員變量
在MFC類中使用托管對象,提供對象的聲明和初始化方法與傳統的方法略有不同。以在文檔類CtestDoc中添加一個托管成員變量為例,聲明托管對象的代碼如下:
public: gcroot
gcroot類型安全包裝模板可以將托管參考類型指針作為成員變量嵌入到非托管類中,該變量就可以像其他類型的變量一樣使用了。在CtestDoc的成員函數InitialDocument中創建這個對象,代碼如下:
BOOL CtestDoc::InitialDocument() { #pragma push_macro("new") #undef new m_ptestDocObj = new test::testDocObject(); #pragma pop_macro("new") }
由於testDocObject是一個托管參考類型,它總被分配在CLR堆上,所以自然不能使用在afx.h中定義的new操作符來直接初始化該對象以避免該托管對象在非托管的本地C++堆上創建導致的錯誤。在托管對象中聲明MFC對象,與常規方法一致。
掌握了上述的基本托管類和對象在傳統MFC項目中的對偶使用方法,就可以保證您充分使用.NET框架所提供的豐富的類庫支持(引用相關的動態鏈接庫並聲明名稱空間是必要的)。如果希望更多地了解托管和非托管C++代碼混用的技術,可以參考Tom Archer與Nishant Sivakumar合著,由Addison Wesley出版社出版的《Extending MFC Applications with the .NET Framework》一書,相信會很有幫助。宿主.NET控件
宿主.NET控件的理論基礎
在.NET Framework的世界裡,功能豐富的.NET控件無疑是光彩奪目的明珠,MFC程序自然想聯姻這些華麗的事物。由於MFC框架不提供對.NET控件的直接支持,從而導致MFC後天的失落(缺乏類似C#、VB.NET特有的可視化設計機制以及自由的控件組織功能),這一點多少成為MFC遠離.NET世界的一種合理的客觀原因。但是,我們注意到:.NET控件本質上就是ActiveX控件,二者之間的重要區別是注冊方式不同——ActiveX控件是全局的,在系統注冊表中注冊;而.NET控件既可以在全局AssemblyCache中注冊,也可以放在局部的目錄中,相應的,在程序中獲取它們相關信息的方式是不同的。但是,一旦.NET控件的基本信息被我們“捕獲”,我們就可以使用與創建ActiveX控件一致的方法將.NET控件創建到MFC項目中。
(圖4:MFC框架中ActiveX控件的創建)
我們知道,MFC是通過COleControlSite類創建ActiveX控件的,由於針對用於ActiveX控件的COleControlSite類不適用於.NET控件,因此必須重新派生一個新類CWFControlSite來提供必要的支持,通過一個CWFControlWrapper類將一個.NET控件包裝在一個CWnd窗體中,並將包裝後的窗體“安置”在CWFControlSite內。CWFControlWrapper類代碼如下:
class CWFControlWrapper : public CWnd { public: CWFControlWrapper(); virtual ~CWFControlWrapper(void); IUnknown *pUnkControl; IUnknown *GetManagedControl() { return pUnkControl; } void SetControlSite(COleControlSite *pSite) { m_pCtrlSite = pSite; } };
下一步,要設計一個通用的CUserCtrlView類(從CView類派生),使得在CWFControlSite中指定的.NET控件可以像在COleControlSite中指定的ActiveX控件一樣顯示給用戶。正象每個ActiveX控件必需用一個CWnd對象進行創建一樣,一個支持.NET控件的CView類需要一個對應的CWnd對象,CWFControlWrapper就是針對這個目的設計的,通過CWFControlWrapper對象,MFC程序可以得到.NET對象對應的IUnknow、IDispatch。稍後我們介紹CUserCtrlView類的具體設計和使用方法。
(圖5:MFC框架中.NET控件的創建)
NET控件的消息處理
一般而言,控件的對話框消息處理是一個極為關鍵的問題,在網上能找到的MFC中宿主控件的解決方法中,均沒有實現.NET控件的對話框消息處理,一個明顯的特征是不能處理“Tab”鍵消息。為此,我們重載了CUserCtrlView的PreTranslateMessage函數:
BOOL CUserCtrlView::PreTranslateMessage(MSG *pMsg) { BOOL bRet = FALSE; if(m_Control.pUnkControl != NULL) { CComQIPtr