這篇文章使用了以下技術(shù):
C++, MFC, .NET Framework, Windows Presentation Foundation
盡管Microsoft .NET Framework在大約五年前就首次公布了,而且2.0版本最近也已經(jīng)發(fā)布了,但是許多C++應(yīng)用程序仍舊是純的非托管代碼。然而C++的開發(fā)人員對.NET Framework的興趣正在快速地增長。并且,許多未來的Windows® API將基于.NET Framework。當(dāng)然,這對于WinFX®的組件來說也是正確的,包括Windows Presentation Foundation,Windows Communication Foundation,和Windows Workflow Foundation。
而且,許多C++開發(fā)人員愿意使用本機(jī)API,而不是在現(xiàn)存的API上進(jìn)行包裝。包裝往往被認(rèn)為是有bug的、慢的以及不靈活的。除此之外,要把像WinFX一樣的大量API映射成為供本機(jī)代碼使用的API,確實(shí)困難。
在大多數(shù)情況下,為C++應(yīng)用程序擴(kuò)展.NET Framework特性比大多數(shù)開發(fā)人員想象的要簡單。Visual C++®包含了一種被稱作是C++ Interop的特性,有時(shí)簡稱為IJW,或“It Just Works”。 使用本特性可以把基于.NET的代碼無縫地集成到已經(jīng)存在的C++源代碼中。
C++ Interop基于兩個(gè)主要的特性。首先,你能使用C++編譯器的/clr開關(guān)將已經(jīng)存在的C++代碼編譯成Microsoft®中間語言(MSIL),從而你的代碼可以使用.NET特性,如垃圾回收、沙箱安全以及大量.NET Framework基類庫中的類型。
另一個(gè)特性是混合模式,它也是同等重要的。它是指托管代碼和非托管代碼的聯(lián)合。當(dāng)把C++代碼編譯成MSIL時(shí),編譯器創(chuàng)建了包含MSIL代碼的托管對象文件,而不是包含本機(jī)匯編代碼的普通的非托管對象文件。連接器能把托管對象文件和非托管對象文件同時(shí)作為輸入。當(dāng)連接器檢測到至少有一個(gè)托管輸入時(shí),它創(chuàng)建一個(gè)同時(shí)包含有托管代碼和非托管代碼的.NET程序集(assembly)。這是它被稱為混合模式的原因(見圖1)。這個(gè)特性對于性能優(yōu)化尤其重要,因?yàn)樗试S你最大程度地減少在托管空間和非托管空間的轉(zhuǎn)換次數(shù)。

圖1 MTS
C++ Interop是你的朋友
我的經(jīng)驗(yàn)表明,C++ Interop是非常可靠的,盡管它有一些限制,但還是值得把它加入到你的開發(fā)工具箱中。要理解這一特性是如何工作的,你應(yīng)當(dāng)認(rèn)識(shí)到C++ Interop是.NET Framework的一個(gè)設(shè)計(jì)特性。作為.NET Framework基礎(chǔ)規(guī)格的通用語言基礎(chǔ)架構(gòu)(CLI),已經(jīng)支持了這一特性。事實(shí)上,CLI的一些方面被定義,僅僅是因?yàn)?/span>C++ Interop需要它們。
例如,托管元數(shù)據(jù)支持全局函數(shù)。然而C#和大多數(shù)面向.NET的其他語言要求所有方法要存在于類的范圍內(nèi)。這一特性的存在是因?yàn)閼?yīng)當(dāng)被映射成基于.NET代碼的C++代碼可以包含全局函數(shù),為了把C++代碼映射成托管代碼,元數(shù)據(jù)需要一個(gè)同等的概念。
在設(shè)計(jì)上,MSIL指令集已經(jīng)支持C++ Interop。為了把C++代碼映射為MSIL代碼,MSIL必須支持所有公共數(shù)據(jù)操作運(yùn)算符,如布爾、算術(shù)以及符點(diǎn)運(yùn)算符。MSIL也支持對虛擬內(nèi)存基于指針的存取。這一特性是在托管代碼中使用本地類型的關(guān)鍵。如果你的C++代碼要存取一個(gè)本地對象的一個(gè)字段,C++/CLI編譯器會(huì)生成MSIL代碼,把本地對象的地址壓到堆棧上,在其上加上字段的偏移,并使用返回地址來存取字段。所有這些成為可能是因?yàn)?/span>MSIL指令集有通過地址來存取虛擬內(nèi)存地址的操作。
另一種IL語言的C++特定的特性是CALLI指令。它可被用于通過函數(shù)指針調(diào)用本地函數(shù)。這個(gè)特性被用于調(diào)用本地類型的虛函數(shù)。這很重要,因?yàn)?/span>COM接口是帶有虛函數(shù)的本地類型。這種COM互操作的方法,與用于其他語言的其他COM互操作技術(shù)相比,具有許多優(yōu)點(diǎn)。
最后值得一提的是,對使用.NET的所有開發(fā)人員最明顯的設(shè)計(jì)影響是組件的文件格式。Java定義了自己的文件格式(.class文件),然而.NET Framework使用標(biāo)準(zhǔn)PE文件格式。因此,基于.NET的組件有很合適的文件擴(kuò)展DLL和EXE。PE文件仍被使用的事實(shí)對于支持混合模式的程序集來說是至關(guān)重要的。
何時(shí)使用C++ Interop
要評估C++ Interop是否適用于你的項(xiàng)目,你應(yīng)當(dāng)了解這個(gè)特性在生成你的項(xiàng)目時(shí)所帶來的影響,對開始者來說,即編譯器設(shè)置,同時(shí),也應(yīng)當(dāng)了解它對于生成過程的輸出結(jié)果的影響。如果使用/clr選項(xiàng)進(jìn)行編譯,代碼嚴(yán)格依賴C運(yùn)行時(shí)庫(CRT)的多線程DLL版本。這意味著項(xiàng)目中的所有文件,包括那些不使用/clr編譯的文件,必須使用CRT的多線程DLL版本。這對MFC項(xiàng)目同樣重要。靜態(tài)MFC庫依賴靜態(tài)CRT庫,它不兼容/clr編譯。因此,你必須確認(rèn)你的MFC項(xiàng)目連接到MFC的DLL版本。
這兒還有一些關(guān)于執(zhí)行時(shí)間的話題。初始化CLR占用一定量的時(shí)間,JIT編譯也有一些附加時(shí)間。因此,你也許感覺啟動(dòng)時(shí)間稍微變長。然而,對大多數(shù)項(xiàng)目而言,這應(yīng)當(dāng)不是一個(gè)大的問題。
由托管到非托管切換的方法調(diào)用,比通常的方法調(diào)用花費(fèi)的時(shí)間更長。我已提到過,你可以通過判定你的應(yīng)用的某些特定部分將被編譯成托管代碼,來顯著地減少托管和非托管代碼之間的切換。調(diào)用轉(zhuǎn)換__clrcall是另一種可以顯著地減少切換次數(shù)的重要優(yōu)化特性,但這一特性超出本文的范圍。
在大多數(shù)情形下,即時(shí)(JIT)編譯代碼本身并不是性能顯著下降的原因。事實(shí)上,JIT編譯器可以完成一些優(yōu)化,而C++編譯器和連接器卻不可能完成。例如,JIT編譯器可以針對目標(biāo)機(jī)器的處理器架構(gòu)優(yōu)化代碼。與C++編譯器相反,JIT編譯器提供組件間內(nèi)聯(lián)。這可能是一個(gè)非常有效的優(yōu)化,因?yàn)榫幾g成帶有組件間內(nèi)聯(lián)的代碼可以用于未來的優(yōu)化。
除了在這兒提到的話題外,還有一些其他相關(guān)的領(lǐng)域,我將不再詳細(xì)地討論。它們中的大部分可以通過正確地修改編譯器和連接器設(shè)置,很容易地被解決。最佳的編譯器設(shè)置并不總是直截了當(dāng)?shù)摹R恍┰O(shè)置打開了對C++ Interop編譯強(qiáng)制的特性,而一些其他的設(shè)置關(guān)閉了與/clr編譯不兼容的,或者已經(jīng)被Microsoft中間語言以另一種方法解決了的特性。
在MFC應(yīng)用中的Windows窗體控件
所有使用/clr選項(xiàng)編譯的源文件可以使用.NET Framework基類庫中的類型。你可以像你一直使用它們那樣,來使用大部分的基類庫。有一些情況需要特別注意。在MFC應(yīng)用中集成Windows窗體控件就是一個(gè)例子。
你不能在需要CWnd*時(shí)傳遞System::Windows::Forms::Control^。然而,Windows窗體API和MFC共享公共的基礎(chǔ):好的古老的User32.dll。這兩種API都提供了到HWNDs的一些后門。MFC的CWnd類提供一個(gè)到HWND的轉(zhuǎn)換操作符,而Windows窗體控件類在命名空間System::Windows::Forms中實(shí)現(xiàn)了IWin32Window接口,以暴露出窗口句柄:
public interface IWin32Window {
property IntPtr Handle {
IntPtr get ();
}
};
然而,從一個(gè)Window對象中獲得窗口句柄通常是不夠的。MFC提供一些類來支持在不同場景下處理Windows窗體控件。這些類被定義在afxwinforms.h中。
這些類中最重要的是CWinFormsControl,它被定義在命名空間Microsoft::VisualC::MFC中。這個(gè)類允許在一個(gè)MFC對話框或CDialog派生類中宿主一個(gè)Windows窗體控件。它是帶有一個(gè)模板參數(shù)的模板類,參數(shù)被用于傳遞要宿主的Windows窗體的類型。類型是要稍后實(shí)例化的具體類型,或者是具體類型的基類。CWinFormsControl模板很簡單。它只有兩個(gè)有趣的特性:它繼承于CWnd,提供了一個(gè)自解釋方法CreateManagedControl的幾種重載形式。
如果你想在MFC對話框或CDialog派生類中宿主一個(gè)Windows窗體控件,在你的類中定義一個(gè)類型為CWinFormsControl<TWinFormsCtrl>的成員變量,并選擇一種CreateManagedControl的重載形式。如果你實(shí)現(xiàn)一個(gè)CDialog派生類,創(chuàng)建托管控件的好的候選是OnInitDialog。對大多數(shù)其他情形,OnCreate(WM_CREATE消息處理)是最好的選擇。對于對話框類而言,有一種重載形式尤其有趣。這種重載方式期望在對話框資源中加入一個(gè)靜態(tài)控件作為占位符。放置靜態(tài)控件,以便它有Windows窗體控件應(yīng)當(dāng)有的位置和大小,并且賦給它一個(gè)唯一的ID,如圖4所示。要實(shí)例化被宿主的控件,如圖2所示調(diào)用CreateManagedControl。此外,你甚至可以使用對話框數(shù)據(jù)交換(DDX)的一個(gè)特別變種,如圖3所示。

圖4 使用ActiveX控件
注意,在MFC應(yīng)用中宿主Windows窗體控件的方式,使用了另一種基于Windows窗體和MFC的技術(shù):ActiveX控件。如圖5列表所示,System::Windows::Forms::Control實(shí)現(xiàn)了一些接口,它們對于OLE和ActiveX控件熟悉的老手們來說很熟悉。在CreateManagedControl中,Windows窗體控件通過gcnew操作符被實(shí)例化,并像一個(gè)普通ActiveX控件一樣被就地激活。
還要注意,CWinFormsControl重載了->操作符,返回被宿主的控件。這允許你通過簡單的賦值來設(shè)置按鈕的Text屬性:
m_wfBtn.Text = "Click me!";
這種處理方式不如在屬性窗口中修改控件屬性那樣簡潔,但是如果你適應(yīng)了現(xiàn)存的有限的設(shè)計(jì)器支持,Windows窗體控制的世界將向你可信的老的MFC對話框開放。
處理控件事件
這兒還要討論一些其他問題。到目前為止,按鈕的單擊事件還沒有被處理。因?yàn)槟阌幸粋€(gè)像ActiveX控件一樣被宿主的Windows窗體控件,處理事件有兩種選擇:由ActiveX控件提供的連接點(diǎn)方式,和Windows窗體控件的事件成員方式。如果Windows窗體控件準(zhǔn)備支持基于COM的事件,連接點(diǎn)方案才成為可能。大多數(shù)Windows窗體控件并不符合這種情形,所以以.NET方式處理事件是唯一的方法。托管類中的事件是一種特別的類型成員,允許事件處理程序委托(event handler delegate)的注冊和注銷。以下的代碼行展示了如何注冊按鈕的單擊事件:
m_wfBtn.Click +=<單擊事件的一個(gè)事件處理程序委托>
要提供這樣一個(gè)委托,需要如下語法的一個(gè)函數(shù):
void EventHandler(Object^ sender, EventArgs^ e);
很不幸,在CDialog派生類中實(shí)現(xiàn)這樣一個(gè)方法還不夠。
委托目標(biāo)函數(shù)必須是托管類的成員函數(shù),CDialog派生類是一個(gè)本地類。Visual C++提供了一個(gè)頭文件,\Msclr\Event.h,針對這個(gè)問題給出了通用解決方案。當(dāng)你想用本地C++類的方法來處理.NET事件時(shí),可以使用頭文件中的定義。正如我稍后要討論的,當(dāng)你要集成Windows Presentation Foundation的Visuals特性時(shí),同樣可以使用這一頭文件。因?yàn)檫@一頭文件在如此多的不同的情形下是有用的,所以深入地討論它提供的解決方案是有意義的。
為了處理Windows窗體控件的事件,你可以實(shí)現(xiàn)包含消息處理程序的一個(gè)托管類。C++ Interop允許你以優(yōu)雅的方式去處理,因?yàn)橥泄茴惪梢员磺度氲?/span>CDialog派生類中。
class CMyDialog : public CDialog {
ref class nested_managed_class {
void OnWFBtnClick(Object^ sender, EventArgs^ e) {
// 在這兒處理按鈕單擊事件
}
};
// 其他成員
};
最便利的處理按鈕單擊事件的方法是,把方法調(diào)用移交給CDialog派生類的一個(gè)成員函數(shù)。最終處理單擊事件的方法,可以很容易地存取派生類的其他成員,就像你期望事件處理程序工作的那樣。包含委托目標(biāo)的托管類最終成為了一個(gè)簡單的代理類型。盡管這樣一個(gè)代理類型和它的代理事件處理程序可以由定義在Event.h中的宏來創(chuàng)制,但是讓我們首先看看如何進(jìn)行手工定義(見圖6)。
如你所見,委托代理有一個(gè)帶有CMyDialog*參數(shù)的構(gòu)造函數(shù)。要把方法調(diào)用移交給CDialog派生類,這樣一個(gè)構(gòu)造函數(shù)是必要的。這一委托代理類型可以用于本對話框類中的所有Windows窗體控件的所有事件處理程序。
因?yàn)楸镜仡愔胁荒苡兄赶蚶厥諏ο蟮木浔鳛樽侄危栽谂缮愔性黾右粋€(gè)delegate_proxy_type的成員變量是不可能的。使用gcroot模板,你可以繞過這一限制。因此,一個(gè)類型為gcroot<delegate_proxy_type>的成員變量是必須的。然而,還有一個(gè)問題:委托代理類型的gcrooted引用是如何被正確地初始化的?當(dāng)需要delegate_proxy_type的句柄時(shí),調(diào)用一個(gè)單獨(dú)的方法來處理。
運(yùn)用圖6的代碼,如下例所示,委托能被容易地注冊:
virtual BOOL OnInitDialog() {
CDialog::OnInitDialog();
this->m_wfBtn->Click +=
gcnew System::EventHandler(get_proxy(this),
&delegate_proxy_type::OnWFBtnClick);
return TRUE;
}
寫所有這些代碼的工作量非常大,而這些僅僅是為了在CDialog派生類中處理Windows窗體控件的事件。Event.h包含了宏和模板,使得你可以用很少的代碼行完成同樣的甚至更多的功能,如圖7所示。
每個(gè)新版本的MFC至少有一個(gè)新的映射(map)。如你所見,目前版本引入了委托映射。宏BEGIN_DELEGATE_MAP定義了包含有委托目標(biāo)的delegate_proxy_type。每一個(gè)EVENT_DELEGATE_ENTRY行在托管類中增加了一個(gè)目標(biāo)方法。END_DELEGATE_MAP只是結(jié)束了托管類。最后,MAKE_DELEGATE實(shí)例化一個(gè)指向delegate_proxy_type的目標(biāo)方法的委托。實(shí)際上,這些宏做的事情比我剛才討論的還要更多。它們甚至為以下場景做好了準(zhǔn)備,在托管類發(fā)生的事件要被發(fā)送給一個(gè)不再存在的本地對象。
作為對話框的控件
另一個(gè)簡單但有效的助手是CWinFormsDialog模板。你可以使用它以模態(tài)或非模態(tài)對話框的形式來展示一個(gè)Windows窗體控件。這個(gè)類相當(dāng)簡單。它擴(kuò)展CDialog來提供MFC對話框的標(biāo)準(zhǔn)行為。
除此以外,它使用一個(gè)CWinFormsControl成員變量來宿主Windows窗體控件,用被宿主控件的Text屬性值來初始化對話框的標(biāo)題,設(shè)置對話框的初始大小,以便控件恰好嵌入其中,并確保當(dāng)父窗口大小在改變時(shí),調(diào)整被宿主控件的大小。
下面的一行代碼實(shí)例化一個(gè)宿主YourWinFormsDlgControl的臨時(shí)對話框,并以一個(gè)模態(tài)對話框形式顯示它:
CWinFormsDialog<YourWinFormsDlgControl>().DoModal();
然而,你應(yīng)當(dāng)避免這樣來使用,因?yàn)檫@樣無法使用DoModal的對話框返回值。對于更實(shí)際的情況,考慮從CWinFormsDialog<YourWinFormsDlgControl>中派生你自己的類。這允許你重載OnInitDialog,設(shè)置被嵌入的Windows窗體控件的屬性,或處理被宿主控件的事件。我之前描述的Event.h中的宏在這種情況下也是有用的。要獲得更多信息,參見msdn2.microsoft.com/ahdd1h97.htm。
Windows窗體視圖
最后,你可以以視圖的方式宿主Windows窗體控件。CWinFormsView是這種情形下你要的小助手。不像CWinFormsDialog一樣,它并不是一個(gè)模板類。然而,也有很多相似之處。它們都把實(shí)際的控件宿主留到了CWinFormsControl模板中,并且它們都處理被宿主控件的大小調(diào)整。
為了用一個(gè)MFC對話框來宿主Windows窗體控件,你必須從CWinFormsView中繼承你的視圖類。以下代碼展示了一個(gè)簡單的基于CWinFormsView的視圖類的聲明:
class CMyWinFormsBasedView : public CWinFormsView
{
CMyWinFormsBasedView ();
DECLARE_DYNCREATE(CMyWinFormsBasedView)
};
如果你的視圖類是由向?qū)傻模銘?yīng)當(dāng)確保不僅在你的視圖類定義的基類列表中,而且在它的實(shí)現(xiàn)中,改換成CWinFormsView。你可能要改變宏的參數(shù),如IMPLEMENT_DYNAMIC和BEGIN_MESSAGE_MAP宏。要實(shí)例化被宿主的Windows窗體控件,CWinFormsView(你的視圖基類)的構(gòu)造函數(shù)得到一個(gè)指向Windows窗體控件的System::Type對象的句柄。視圖類的實(shí)現(xiàn)也非常簡單,如下所示:
IMPLEMENT_DYNCREATE(CMyWinFormsBasedView, CWinFormsView)
CMyWinFormsBasedView:: CMyWinFormsBasedView ()
: CWinFormsView(WinFormsViewControl::typeid) {}
初看起來,這段代碼對于實(shí)際情況來說太簡單,但是在一些情況下,這確實(shí)就是你所需要的全部。視圖類充當(dāng)了位于視圖控件中的真實(shí)實(shí)現(xiàn)的簡單代理。
一些視圖的實(shí)現(xiàn)重載了MFC CView基類中的虛函數(shù)。要讓本地視圖充當(dāng)一個(gè)簡單的代理,應(yīng)當(dāng)在本地視圖中重載這些方法,并把這些調(diào)用移交給視圖中Windows窗體控件的對等方法。對CView中的三個(gè)最重要的重載來說,基類CWinFormsView已經(jīng)實(shí)現(xiàn)了這樣的移交。這三個(gè)方法是OnInitialUpdate、OnUpdate以及OnActivateView。傳遞基于定義在Mfcmifc80.dll程序集中的托管接口Microsoft::VisualC::MFC::IView。為了重載這些方法,在視圖的Windows窗體類中實(shí)現(xiàn)這一接口,如圖8所示。
除了重載虛函數(shù)以外,視圖還實(shí)現(xiàn)了命令處理程序。在本地視圖類中實(shí)現(xiàn)一個(gè)命令處理程序并把它傳遞給Windows窗體控件是可能的,但是因?yàn)檫@是一種常見的場景,所以MFC提供了一個(gè)更為便利的解決方案。在這種場景下,Mfcmifc80.dll程序集包含了更多的托管助手類型。要從MFC的命令傳送基礎(chǔ)結(jié)構(gòu)中接收命令消息,Windows窗體控件必須實(shí)現(xiàn)Microsoft::VisualC::MFC::ICommandTarget接口:
interface class ICommandTarget
{
void Initialize(ICommandSource^ commandSource);
};
當(dāng)實(shí)現(xiàn)ICommandTarget的視圖被創(chuàng)建時(shí),在那個(gè)視圖上,ICommandTarget::Initialize被調(diào)用,一個(gè)新命令源對象的句柄被當(dāng)作參數(shù)傳遞。這個(gè)參數(shù)的類型是ICommandSource^。參數(shù)所指的對象是一個(gè)許多處理程序的容器。這些處理程序?qū)τ?/span>MFC開發(fā)人員來說,聽起來應(yīng)當(dāng)很熟悉:它們是當(dāng)一個(gè)命令發(fā)出時(shí)被調(diào)用的命令處理程序;控制控件的UI是否使能、選中,或用單選按鈕裝飾,以及在命令UI中顯示什么樣的文字的命令UI處理程序。
要注冊一個(gè)處理程序,像AddCommandHandler和AddCommandUIHandler一樣的方法,可以在命令源上被調(diào)用。這些方法期望兩個(gè)參數(shù):指代命令ID的無符號(hào)整數(shù),和被用于傳遞控件處理程序函數(shù)的委托。因?yàn)槊钐幚沓绦蚝兔?/span>UI處理程序有著不同的簽名,在命名空間Microsoft::VisualC::MFC中定義了兩個(gè)不同的委托類型:
delegate void CommandHandler(unsigned int);
delegate void CommandUIHandler(unsigned int,
Microsoft::VisualC::MFC::ICommandUI^);
圖9展示了如何為ID_EDIT_PASTE命令注冊一個(gè)命令處理程序。
當(dāng)視圖要處理一個(gè)命令時(shí),委托被用于調(diào)用一個(gè)視圖的成員函數(shù)。命令ID被作為參數(shù)來傳遞。如果每個(gè)命令有自己私有的處理程序函數(shù),這個(gè)參數(shù)并不重要,但是一個(gè)簡單的方法可以被用于多個(gè)命令的處理程序函數(shù)。AddCommand[UI]RangeHandler允許你為多個(gè)命令注冊一個(gè)委托。
命令UI處理程序委托還有一個(gè)類型為ICommandUI^的參數(shù)。使用這個(gè)參數(shù)傳遞的句柄是MFC CCmdUI類的包裝器,它允許你控制如菜單項(xiàng)和工具條按鈕等界面元素的表現(xiàn)和可用性。
除了在ICommandSource::Initialize中增加命令處理程序外,在Windows窗體控件的成員變量中存儲(chǔ)ICommandTarget句柄也是很有用的。它給你提供了以后增加和移除命令處理程序的選擇,你甚至可以通過ICommandTarget::SendMessage或ICommandTarget::PostMessage來同步或異步發(fā)出命令事件。
通向Avalon之橋
因?yàn)?/span>Windows Presentation Foundation還沒有最終發(fā)布,當(dāng)前的MFC版本并沒有在CWnds和CDialogs中集成Windows Presentation Foundation的Visuals提供助手類和模板。然而,這并不意味著在MFC應(yīng)用中集成Visuals是不可能的。事實(shí)上,即使沒有這些小助手類,仍然只需要幾行代碼就能在CWnd或CDialog中宿主一個(gè)Visual。
這種集成的關(guān)鍵特性來自于Windows Presentation Foundation自身。Windows.PresentationCore.dll程序集提供了一個(gè)強(qiáng)有力的類HwndSource,它位于命名空間System::Windows::Interop中。這個(gè)類提供了HWND和Visual之間的橋。要宿主的USER32窗口對象看來像一個(gè)有通常HWND的子窗口。使用它的RootVisual屬性,你可以進(jìn)入Windows Presentation Foundation的新世界。(注意,這兒的信息基于Windows Presentation Foundation的一個(gè)預(yù)發(fā)布版本,也許在最終發(fā)布時(shí)會(huì)改變。)
HwndSource構(gòu)造函數(shù)期望HwndSourceParameters的值類型作為參數(shù)。如圖10所示,這一值類型包含了與調(diào)用Win32函數(shù)CreateWindowEx所傳遞參數(shù)相似的信息。圖11展示了在OnInitDialog實(shí)現(xiàn)中如何使用HwndSource。
總結(jié)
通過使用C++ Interop,托管代碼可以被無縫地集成到現(xiàn)存的C++源代碼中。集成是Visual C++本來就具有的、被支持的特性,對Windows窗體的MFC支持可以被認(rèn)為是對此強(qiáng)有力的證據(jù)。為了簡化這種集成,提供了幾種助手類和模板。集成層并不復(fù)雜,也不難于使用。
還有一件事情,是把這些特性無縫地集成到Visual Studio的向?qū)е小T诖蠖鄶?shù)的場景中,因?yàn)楹芏鄬?shí)現(xiàn)將被遺留到被宿主的Windows窗體控件中,所以這并不是一個(gè)大的問題,而Windows窗體控件在Visual Studio中得到了很好的支持。當(dāng)Windows Presentation Foundation發(fā)布時(shí),為了在MFC應(yīng)用中集成Visuals,提供對等支持的可能性是很大的。即使沒有這種支持,在MFC應(yīng)用中宿主Visuals仍然是很容易的。
Marcus Heege works as a course author and trainer for DevelopMentor and provides consulting for different IT companies. He regularly posts his thoughts about C++/CLI and .NET in his blog at www.heege.net.
From the May 2006 issue of MSDN Magazine.
圖2 創(chuàng)建一個(gè)被宿主控件
using Microsoft::VisualC::MFC;
using System::Windows::Forms;
class CMyDialog : public CDialog
{
public:
CMyDialog() : CDialog(CMyDialog::IDD, NULL) {}
enum { IDD = IDD_MYDIALOG };
private:
CWinFormsControl<Button> m_wfBtn;
public:
BOOL OnInitDialog() {
CDialog::OnInitDialog();
return this->m_wfBtn.CreateManagedControl(
WS_VISIBLE | WS_CHILD, IDC_WFBTN, this));
}
}
圖3 宿主一個(gè)有對話框數(shù)據(jù)交換(DDX)的控件
using Microsoft::VisualC::MFC;
using System::Windows::Forms;
class CMyDialog : public CDialog
{
public:
CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}
enum { IDD = IDD_MYDIALOG };
private:
CWinFormsControl<Button> m_wfBtn;
protected:
virtual void DoDataExchange(CDataExchange* pDX) {
DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn);
}
};
圖5 接口
System::Windows::Forms::Control
|
IOleObject
|
IOleControl
|
IOleInPlaceObject
|
IOleInPlaceActiveObject
|
IOleWindow
|
IOleViewObject
|
IOleViewObject2
|
IPersist
|
IPersistStreamInit
|
IPersistPropertyBag
|
IPersistStorage
|
IQuickActivate
|
圖6 CMyDialog
class CMyDialog : public CDialog
{
public:
ref class delegate_proxy_type;
gcroot<delegate_proxy_type^> m_gc_managed_native_delegate_proxy;
delegate_proxy_type^ get_proxy(CMyDialog* pNativeTarget) {
if((delegate_proxy_type^)m_gc_managed_native_delegate_proxy ==
nullptr)
{
m_gc_managed_native_delegate_proxy =
gcnew delegate_proxy_type(pNativeTarget);
}
return (delegate_proxy_type^)m_gc_managed_native_delegate_proxy;
}
ref class delegate_proxy_type {
CMyDialog* m_p_native_target;
public:
delegate_proxy_type(CMyDialog* pNativeTarget) :
m_p_native_target(pNativeTarget) {}
void OnWFBtnClick(Object^ sender, EventArgs^ e) {
this->m_p_native_target->OnWFBtnClick(sender, e);
}
};
void OnWFBtnClick(Object^ sender, EventArgs^ e) {
this->m_wfBtn->Text = "Thanks for clicking";
}
// other members
};
圖7 簡化CMyDialog
class CMyDialog : public CDialog
{
public:
CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}
enum { IDD = IDD_MYDIALOG };
private:
CWinFormsControl<Button> m_wfBtn;
protected:
virtual void DoDataExchange(CDataExchange* pDX) {
DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn);
}
public:
BEGIN_DELEGATE_MAP(CMyDialog)
EVENT_DELEGATE_ENTRY(OnWFBtnClick, Object^, EventArgs^)
END_DELEGATE_MAP()
void OnWFBtnClick(Object^ sender, EventArgs^ e) {
this->m_wfBtn->Text = "Thanks for clicking";
}
public:
virtual BOOL OnInitDialog(){
CDialog::OnInitDialog();
this->m_wfBtn->Click += MAKE_DELEGATE(System::EventHandler,
OnWFBtnClick);
return TRUE;
}
};
圖8 重載IView
#using <mfcmifc80.dll>
using Microsoft::VisualC::MFC::IView;
public ref class MyWinFormsViewControl :
public System::Windows::Forms::UserControl,
public IView
{
...
protected: // IView implementation
virtual void OnInitialUpdate() = IView::OnInitialUpdate {
// implementation
}
virtual void OnUpdate() = IView::OnUpdate {
// implementation
}
virtual void OnActivateView(bool activate) = IView::OnActivateView {
// implementation
}
};
圖9 注冊命令處理程序
public ref class MyWinFormsViewControl :
public System::Windows::Forms::UserControl,
public ICommandTarget
{
...
protected: // ICommandTarget implementation
virtual void RegisterCmdHandlers(ICommandSource^ cmdSrc) =
ICommandTarget::Initialize
{
cmdSrc->AddCommandHandler(ID_EDIT_PASTE,
gcnew CommandHandler(this, &WinFormsView::OnEditPaste));
cmdSrc->AddCommandUIHandler(ID_EDIT_PASTE,
gcnew CommandUIHandler(this,
&WinFormsView::OnUpdateEditPaste));
// register further command handlers ...
}
void OnEditPaste(unsigned int)
{
// implement command handler
}
void OnUpdateEditPaste(unsigned int, ICommandUI^ cmdUI)
{
cmdUI->Enabled = ...; // your logic here
}
};
圖10 CreateWindowEx和HwndSourceParameters
CreateWindowEx Arguments
|
HwndSourceParameters Properties
|
LPCTSTR lpClassName
|
N/A
|
N/A
|
WindowClassStyle
|
LPCTSTR lpWindowName
|
WindowName
|
DWORD dwStyle
|
WindowStyle
|
DWORD dwExStyle
|
ExtendedWindowStyle
|
int x
|
PositionX
|
int y
|
PositionY
|
int nWidth
|
Width
|
int nHeight
|
Height
|
HWND hWndParent
|
ParentWindow
|
HMENU hMenu
|
N/A
|
HINSTANCE hInstance
|
N/A
|
圖11 使用HwndSource
BOOL CWPFDemoDialog::OnInitDialog()
{
__super::OnInitDialog();
CRect rectClient;
this->GetClientRect(&rectClient);
System::Windows::Interop::HwndSourceParameters hwsPars;
hwsPars.ParentWindow = System::IntPtr(this->m_hWnd);
hwsPars.WindowStyle = WS_CHILD | WS_VISIBLE;
hwsPars.PositionX = 0;
hwsPars.PositionY = 0;
hwsPars.Width = rectClient.Width();
hwsPars.Height = rectClient.Height();
System::Windows::Interop::HwndSource^ hws;
hws = gcnew System::Windows::Interop::HwndSource(hwsPars);
using System::Windows::Controls::MonthCalendar;
MonthCalendar^ mc = gcnew MonthCalendar;
hws->RootVisual = mc;
return TRUE;
}
詞匯表
assignment
|
賦值
|
Common Language Infrastructure
|
通用語言基礎(chǔ)構(gòu)造
|
command-routing infrastructure
|
命令傳送基礎(chǔ)結(jié)構(gòu)
|
deregistration
|
撤銷登記
|
event handler delegates
|
事件處理程序委托
|
fields
|
字段
|
forward
|
移交
|
helper
|
助手
|
instantiates
|
實(shí)例化
|
interoperability
|
互操作性
|
in-place activated
|
就地激活
|
just-in-time
|
實(shí)時(shí)
|
map
|
映射
|
mix and match
|
混合搭配
|
native code
|
本地代碼
|
placeholder
|
占位符
|
template class
|
模板類
|
Windows Forms
|
Windows 窗體
|
posted on 2008-08-15 21:06
cpsprogramer 閱讀(2850)
評論(1) 編輯 收藏 引用 所屬分類:
VC++