| 導購 | 订阅 | 在线投稿
分享
 
 
 

經典與現代的結合:在MFC中集成RAD.NET框架

來源:互聯網  2008-06-01 02:06:40  評論

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)。

經典與現代的結合:在MFC中集成RAD.NET框架

(圖1:打開托管編譯支持開關)

對偶.NET對象

在打開托管擴展編譯開關以後,您就可以在MFC項目中使用托管對象了,通常的做法是:爲每個重要的MFC對象匹配一個托管對象以形成一個對偶對,彼此匹配的對象包含指向對方的指針,這樣,其他.NET對象可以通過對偶對中的.NET對象操作MFC對象;而其他MFC對象可以通過對偶對中的MFC對象操作.NET對象(如圖2)。

經典與現代的結合:在MFC中集成RAD.NET框架

(圖2:對偶托管對象)

在Visual Studio .NET 中,沒有提供關于添加托管C++類對象的向導,因此,你可以先添加一個基于托管C++的Component對象(如圖3)。

經典與現代的結合:在MFC中集成RAD.NET框架

(圖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<test::testDocObject*> m_ptestDocObj;

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項目中。

經典與現代的結合:在MFC中集成RAD.NET框架

(圖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類的具體設計和使用方法。

經典與現代的結合:在MFC中集成RAD.NET框架

(圖5:MFC框架中.NET控件的創建)

NET控件的消息處理

一般而言,控件的對話框消息處理是一個極爲要害的問題,在網上能找到的MFC中宿主控件的解決方法中,均沒有實現.NET控件的對話框消息處理,一個明顯的特征是不能處理「Tab」鍵消息。爲此,我們重載了CUserCtrlView的PreTranslateMessage函數:

以下是引用片段:

BOOL CUserCtrlView::PreTranslateMessage(MSG *pMsg)

{

BOOL bRet = FALSE;

if(m_Control.pUnkControl != NULL)

{

CComQiptr<IOleInPlaceActiveObject>

spInPlace(m_Control.pUnkControl);

if(spInPlace)

bRet =(S_OK == spInPlace->

TranslateAccelerator(pMsg)) ?

TRUE : FALSE;

}

if(CView::PreTranslateMessage(pMsg))

return TRUE;

CFrameWnd *pFrameWnd = GetTopLevelFrame();

if(pFrameWnd != NULL

&& pFrameWnd->m_bHelpMode)

return FALSE;

// start with first parent frame

pFrameWnd = GetParentFrame();

while(pFrameWnd != NULL)

{

if(pFrameWnd->PreTranslateMessage(pMsg))

return TRUE;

pFrameWnd = pFrameWnd->GetParentFrame();

}

return bRet;

}

這樣可以使得CUserCtrlView可以正確的處理.NET Control的對話框消息。

回歸RAD世界

接下來我們看看如何在工程中插入一個.NET用戶自定義控件。我們增加一個新的托管類testControl,代碼如下

以下是引用片段:

#pragma once

...

namespace test

{

public __gc class testControl :

public System::Windows::Forms::UserControl

{

public:

testControl(void)

{

InitializeComponent();

}

protected:

void Dispose(Boolean disposing)

{

if(disposing && components)

components->Dispose();

__super::Dispose(disposing);

}

private:

System::Windows::Forms::Label *label1;

System::ComponentModel::Container

*components;

void InitializeComponent(void)

{

this->label1 = new

System::Windows::Forms::Label();

this->SuspendLayout();

this->label1->Location =

System::Drawing::Point(16, 24);

this->label1->Name = S"label1";

this->label1->Size =

System::Drawing::Size(208, 16);

this->label1->TabIndex = 0;

this->label1->Text =

S"Welcome to TZ MFC.NET!";

this->Controls->Add(this->label1);

this->Name = S"testControl";

this->Size =

System::Drawing::Size(240, 160);

this->ResumeLayout(false);

}

};

}

注重,testControl類繼續自UserControl類,用戶控件是開發者創建的任何控件,您可以將多個.NET控件組織在一起,添加功能代碼,然後把它作爲一個更綜合一些的控件來使用,使用每一個用戶控件和使用其他的.NET標准控件的步驟都是沒有區別的。在上面的代碼中,我們自定義的用戶控件僅包含了一個.NET Label控件。

到目前爲止,我們已經可以在原生MFC項目中成功插入.NET控件。然而,因爲上面的.NET控件的插入是純手工方式的,不直觀且很難駕馭,一個聰明的辦法是實現一個集成在Visual Studio .NET IDE中的Wizard,以使得MFC工程中可以直接使用可視設計器,在隨機光盤中,我們提供了相關的Wizard,安裝後您就可以直接在MFC項目中插入並可視化設計.NET用戶控件了。

通過集成的Wizard,傳統的MFC可以與現代的.NET RAD機制完美的結合在一起,使得你既可以得到傳統C++的優雅,又可以享有現代RAD機制的風韻,對資源的整合力度也極大地擴展了。

  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)。    [url=/bbs/detail_1785382.html][img]http://image.wangchao.net.cn/it/1323423648467.jpg[/img][/url]   (圖1:打開托管編譯支持開關)   對偶.NET對象   在打開托管擴展編譯開關以後,您就可以在MFC項目中使用托管對象了,通常的做法是:爲每個重要的MFC對象匹配一個托管對象以形成一個對偶對,彼此匹配的對象包含指向對方的指針,這樣,其他.NET對象可以通過對偶對中的.NET對象操作MFC對象;而其他MFC對象可以通過對偶對中的MFC對象操作.NET對象(如圖2)。    [url=/bbs/detail_1785382.html][img]http://image.wangchao.net.cn/it/1323423663785.jpg[/img][/url]   (圖2:對偶托管對象)   在Visual Studio .NET 中,沒有提供關于添加托管C++類對象的向導,因此,你可以先添加一個基于托管C++的Component對象(如圖3)。    [url=/bbs/detail_1785382.html][img]http://image.wangchao.net.cn/it/1323423663970.jpg[/img][/url]   (圖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<test::testDocObject*> m_ptestDocObj;   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項目中。    [url=/bbs/detail_1785382.html][img]http://image.wangchao.net.cn/it/1323423664207.jpg[/img][/url]   (圖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類的具體設計和使用方法。    [url=/bbs/detail_1785382.html][img]http://image.wangchao.net.cn/it/1323423664366.jpg[/img][/url]   (圖5:MFC框架中.NET控件的創建)   NET控件的消息處理   一般而言,控件的對話框消息處理是一個極爲要害的問題,在網上能找到的MFC中宿主控件的解決方法中,均沒有實現.NET控件的對話框消息處理,一個明顯的特征是不能處理「Tab」鍵消息。爲此,我們重載了CUserCtrlView的PreTranslateMessage函數: 以下是引用片段: BOOL CUserCtrlView::PreTranslateMessage(MSG *pMsg) { BOOL bRet = FALSE; if(m_Control.pUnkControl != NULL) { CComQiptr<IOleInPlaceActiveObject> spInPlace(m_Control.pUnkControl); if(spInPlace) bRet =(S_OK == spInPlace-> TranslateAccelerator(pMsg)) ? TRUE : FALSE; } if(CView::PreTranslateMessage(pMsg)) return TRUE; CFrameWnd *pFrameWnd = GetTopLevelFrame(); if(pFrameWnd != NULL && pFrameWnd->m_bHelpMode) return FALSE; // start with first parent frame pFrameWnd = GetParentFrame(); while(pFrameWnd != NULL) { if(pFrameWnd->PreTranslateMessage(pMsg)) return TRUE; pFrameWnd = pFrameWnd->GetParentFrame(); } return bRet; }   這樣可以使得CUserCtrlView可以正確的處理.NET Control的對話框消息。   回歸RAD世界   接下來我們看看如何在工程中插入一個.NET用戶自定義控件。我們增加一個新的托管類testControl,代碼如下 以下是引用片段: #pragma once ... namespace test { public __gc class testControl : public System::Windows::Forms::UserControl { public: testControl(void) { InitializeComponent(); } protected: void Dispose(Boolean disposing) { if(disposing && components) components->Dispose(); __super::Dispose(disposing); } private: System::Windows::Forms::Label *label1; System::ComponentModel::Container *components; void InitializeComponent(void) { this->label1 = new System::Windows::Forms::Label(); this->SuspendLayout(); this->label1->Location = System::Drawing::Point(16, 24); this->label1->Name = S"label1"; this->label1->Size = System::Drawing::Size(208, 16); this->label1->TabIndex = 0; this->label1->Text = S"Welcome to TZ MFC.NET!"; this->Controls->Add(this->label1); this->Name = S"testControl"; this->Size = System::Drawing::Size(240, 160); this->ResumeLayout(false); } }; }   注重,testControl類繼續自UserControl類,用戶控件是開發者創建的任何控件,您可以將多個.NET控件組織在一起,添加功能代碼,然後把它作爲一個更綜合一些的控件來使用,使用每一個用戶控件和使用其他的.NET標准控件的步驟都是沒有區別的。在上面的代碼中,我們自定義的用戶控件僅包含了一個.NET Label控件。   到目前爲止,我們已經可以在原生MFC項目中成功插入.NET控件。然而,因爲上面的.NET控件的插入是純手工方式的,不直觀且很難駕馭,一個聰明的辦法是實現一個集成在Visual Studio .NET IDE中的Wizard,以使得MFC工程中可以直接使用可視設計器,在隨機光盤中,我們提供了相關的Wizard,安裝後您就可以直接在MFC項目中插入並可視化設計.NET用戶控件了。   通過集成的Wizard,傳統的MFC可以與現代的.NET RAD機制完美的結合在一起,使得你既可以得到傳統C++的優雅,又可以享有現代RAD機制的風韻,對資源的整合力度也極大地擴展了。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有