本文并不是为了奉承C++/CLI的辉煌,也不是为了贬低其它如C#或者VB.NET等语言,相反,这只是一个非官方的、以一个喜欢这种语言的非微软雇员身份来论证C++/CLI有它的自己的唯一的角色,可作为第一流的.NET编程语言。
4. 混合类型
我们知道,C++支持本机类型-总是如此;C++支持CLI类型-本文正是特别强调这一点;它还支持混合类型-具有CLI成员的本机类型和具有本机成员的CLI类型!请尽管考虑所有你能的可能需求。
注意,谈到Whidbey,混合类型实现还不完整;就我从Brandon,Herb和Ronald发表的材料的理解得知,存在这种相当酷的类型--统一模型,它将在Orcas中实现--你能够在本机C++堆上new/delete CLI类型,而且也能够在CLI堆上gcnew/delete本机类型。但既然这是Whidbey以后的东西,本文不讨论统一模型。
在我谈论你何时使用混合类型以前,我想向你说明什么是混合类型。如果你理解混合类型,请跳过下面几段。这里引用Brandon Bray的说法:"一种混合类型,或者是本机类ref类(需要有对象成员),或者是通过声明或继承被分配在垃圾回收堆或本机堆上的。"因此如果你有一个托管类型或者有一个有托管成员的本机类型,你就有了一个混合类型。VC++ Whidbey不直接支持混合类型(统一类型模型是一种Whidbey之后的概念),但是它给我们划定了实现混合类型的条件。让我们开始讨论包含托管成员的本机类型。
ref class R { public: void F(){} //假定 non-trivial ctor/dtor R(){} ~R(){} }; |
在我的例子中,设想该托管类型R有一个non-trivial构造器和一个non-trivial析构器。
class Native { private: gcroot<R^> m_ref; public: Native(): m_ref(gcnew R()){} ~Native() { delete m_ref; } void DoF() { m_ref->F(); } }; |
既然,我不能在我的类中拥有一个R成员,我使用了gcroot模板类(在gcroot.h中声明,但是你要用"#include vcclr.h"),它包装了System::Runtime::InteropServices::GCHandle结构。它是个象类一样的灵敏指针,它重载了运算符->以返回用作模板参数的托管类型。因此在上面类中,我可以使用m_ref,就好象我已经声明它是R^,而且你能在DoF函数中看到这正在起作用。实际上你可以节省delete,这可以通过使用auto_gcroot(类似于std::auto_ptr,在msclr\auto_gcroot.h文件中声明)代替gcroot来实现。下面是一个更好些的使用auto_gcroot的实现。
class NativeEx { private: msclr::auto_gcroot<R^> m_ref; public: NativeEx() : m_ref(gcnew R()){} void DoF() { m_ref->F(); } }; |
下面让我们看相反的情形:一个CLI类的本机成员。
ref class Managed { private: Native* m_nat; public: Managed():m_nat(new Native()){ } ~Managed() { delete m_nat; } !Managed() { delete m_nat; #ifdef _DEBUG throw gcnew Exception("Oh, finalizer got called!"); #endif } void DoF() { m_nat->DoF(); } }; |
我不能定义一个Native对象来作为一个ref类成员,因此需要使用一个Native*对象来代替。我在构造器中new该Native对象,然后在析构器和finalizer中delete它。如果你运行该
工程的调试版,在执行到finalizer时将抛出一个异常- 因此
开发者可以马上添加一个对delete的调用或为他的CLI类型使用栈语义技术。奇怪的是,库开发小组没有建立一个gcroot的反向实现-但这不是个大问题,我们可以自己写。
template<typename T> ref class nativeroot { T* m_t; public: nativeroot():m_t(new T){} nativeroot(T* t):m_t(t){} T* operator->() { return m_t; } protected: ~nativeroot() { delete m_t; } !nativeroot() { delete m_t; #ifdef _DEBUG throw gcnew Exception("Uh oh, finalizer got called!"); #endif } }; |
这仅是个相当简单的灵敏指针实现,就象一个负责本机对象分配/回收的ref类。不管怎样,借助nativeroot模板类,我们可以如下修改托管类:
ref class ManagedEx { private: nativeroot<Native> m_nat; public: void DoF() { m_nat->DoF(); } }; |
好,关于混合类型的最大问题是什么呢?你可能问。最大问题是,现在你能混合使用你的MFC、ATL、WTL、STL代码仓库和.NET框架,并用可能的最直接的方式-只需写你的混合模式代码并编译实现!你可以建立在一个DLL库中建立MFC 类,然后建立一个.NET应用
程序来调用这个DLL,还需要把.NET类成员添加到你的MFC类(也实现可以相反的情况)。
作为一例,设想你有一MFC对话框--它通过一个多行的编辑框接受来自用户的数据-现在,你有一新的要求-显示一个只读编辑框,它将显示当前在该多行编辑框中文本的md5哈希结果。你的队友正在悲叹他们将必须花费几个小时钻研crypto API,而你的上司在担忧你们可能必须要买一个第三方加密库;那正是你在他们面前树立形象的时候,你宣布你将在15分钟内做完这项任务。下面是解决的办法:
添加一个新的编辑框到你的对话框
资源中,并且添加相应的DDX变量。选择/clr编译模式并且添加下列代码到你的对话框的头文件中:
#include <msclr\auto_gcroot.h> using namespace System::Security::Cryptography; |
使用auto_gcroot模板来声明一个MD5CryptoServiceProvider成员:
protected: msclr::auto_gcroot<MD5CryptoServiceProvider^> md5; |
在OnInitDialog过程中,gcnew MD5CryptoServiceProvider成员。
md5 = gcnew MD5CryptoServiceProvider(); |
并且为多行
编辑框添加一个EN_CHANGE处理器:
void CXxxxxxDlg::OnEnChangeEdit1() { using namespace System; CString str; m_mesgedit.GetWindowText(str); array<Byte>^ data = gcnew array<Byte>(str.GetLength()); for(int i=0; i<str.GetLength(); i++) data[i] = static_cast<Byte>(str[i]); array<Byte>^ hash = md5->ComputeHash(data); CString strhash; for each(Byte b in hash) { str.Format(_T("%2X "),b); strhash += str; } m_md5edit.SetWindowText(strhash); } |
这里使用了混合类型:一个本机Cdialog派生类,该类含有一个MD5CryptoServiceProvider成员(CLI类型)。你可以轻易地试验相反的情况(如早期的代码片断已显示的)——可以建立一个Windows表单
应用程序而且可能想利用一个本机类库--这不成问题,使用上面定义的模板nativeroot即可。