在C++中反射调用.NET的方法(一)
为什么要在C++中调用.NET
一般情况下,我们常常会在.NET程序中调用C/C++的程序,使用P/Invoke方式进行调用,在编写代码代码的时候,首先要导入DLL文件,然后在根据C/C++的头文件编写特殊的C#平台调用代码,例如像下面这个样子:
[DllImport("Interop.dll",EntryPoint="Multiply",CharSet=CharSet.Ansi)] staticexternintMultiply(intfactorA,intfactorB);
详细的过程,可以参考之前我这篇文章:《C#调用C和C++函数的一点区别》
有时候,我们也会有在C++中调用.NET的需求,比如我们在维护一个大型的C++应用程序,它年代久远,现在需要增加一些新功能,而这些功能在.NET中已经有了,只需要调用它即可,如果为了方便想要用.NET重写这个C++应用程序是不太现实的,幸好,C++/CLI提供了一个简便的方案使得可以在C++中直接编写.NET程序,所以C++/CLI代表托管和本地编程的结合,可以在托管代码中直接使用本地代码,也可以反过来,这样结合了C++本地代码的高效性和.NET代码的强大性,看起来是非常有潜力的。
使用C++/CLI进行.NET编程
要进行C++/CLI编程,只需要进行下面的步骤:
1,添加.NET程序集的应用;
2,修改C++项目属性,配置属性->公共语言运行时支持-公共语言运行时支持(/clr)
然而,为了保持C++与.NET应用程序的独立性,要求不能将.NET的DLL文件放到C++的应用程序目录下,因此上述步骤1不可行,需要在C++代码中使用反射来调用.NET。
注意,本文说的C++反射调用,不是对C++自身进行封装的反射功能,而是在C++/CLI代码中反射调用.NET代码,原理上跟你在.NET应用中反射调用另外一个.NET的程序集一个道理。
首先,我们建立一个名字叫CppNetTest的解决方案,添加3个项目:
1,CppConsoleTest---一个C++控制台项目,在项目中更改属性支持CLR;
2,NetApp--一个.NET控制台应用程序,作为对比示例代码,方便编写C++/CLI代码参考;
3,NetLib--一个.NET类库程序集,它将被1和2项目进行反射调用。
我们先在NetLib项目写一个简单的.NET类,这个类的方法内部没有复杂的业务逻辑代码,仅仅用来供反射调用测试:
namespaceNetLib { publicclassUser { staticList<IUserInfo>UserDb=newList<IUserInfo>(); publicintGetUserID(stringIdString) { intresult=0; int.TryParse(IdString,outresult); returnresult; } publicDateTimeGetUserBirthday(intuserId) { returnnewDateTime(1980,1,1); } publicIUserInfoGetUserByID(intuserId) { IUserInfouserinfo=EntityBuilder.CreateEntity<IUserInfo>(); userinfo.ID=userId; userinfo.Name="姓名_"+userId; userinfo.Birthday=newDateTime(1980,1,1); returnuserinfo; } //返回List或者数组,不影响C++调用 publicList<IUserInfo>GetUsers(stringlikeName) { List<IUserInfo>users=newList<NetLib.IUserInfo>(); for(inti=0;i<10;i++) { IUserInfouserinfo=GetUserByID(i); userinfo.Name+=likeName; users.Add(userinfo); } //returnusers.ToArray(); returnusers; } publicboolSaveUsers(IList<IUserInfo>users) { UserDb.AddRange(users); returntrue; } publicIUserInfoCreateUserObject() { returnEntityBuilder.CreateEntity<IUserInfo>(); } publicboolSaveUsers2(IEnumerable<Object>para) { varusers=fromuinpara selectuasIUserInfo; returnSaveUsers(users.ToList()); } } }
在CppConsoleTest项目的头文件中,添加一个UserProxy.h的C++头文件,在文件中添加下面的命名空间:
usingnamespaceSystem; usingnamespaceSystem::Reflection; usingnamespaceRuntime::InteropServices; usingnamespaceSystem::Collections;
这样我们就可以使用反射相关的类型了。
在UserProxy类中,先编写我们需要的构造函数:
publicrefclassUserProxy { private: String^assemblyFile;//"..\\NetLib\\bin\\Debug\\NetLib.dll" Object^dotnetObject; Type^entityBuilderType; String^className="NetLib.User"; EntityHelper^helper; public: UserProxy(String^assemblyFile) { this->assemblyFile=assemblyFile; Assembly^ass=Assembly::LoadFrom(this->assemblyFile); this->dotnetObject=ass->CreateInstance(className); String^sodPath=System::IO::Path::Combine(System::IO::Path::GetDirectoryName(this->assemblyFile),"PWMIS.Core.dll"); /*Assembly^ass_sod=Assembly::LoadFrom(sodPath); this->entityBuilderType=ass_sod->GetType("PWMIS.DataMap.Entity.EntityBuilder");*/ helper=gcnewEntityHelper(sodPath); } }
注意我们的C++/CLI的类必须是“引用”类型,所以需要加关键字ref,即:
publicrefclassUserProxy{}
所有的.NET引用类型,在使用的时候,都必须在类型名字后加^符号,例如下面定一个.NET字符串类型变量:
String^assemblyFile;
带^符号的变量,在C++/CLI中称为“句柄”对象,用来跟C++本地代码的“指针”相区别。
在C++中,类的成员用->符号调用,命名空间或者类的静态成员,用::调用,例如上面的构造函数中的代码:
Assembly^ass=Assembly::LoadFrom(this->assemblyFile);
注意:在本例中需要.NET类库项目引用PDF.NETSOD框架,在项目的“管理Nuget程序包”里面搜索PDF.NET.SOD.Core添加此引用即可。
学会了这些C++的基础语法,那么编写C++/CLI代码就没有主要的障碍了。
在C++/CLI中使用反射
反射调用第一个.NET类的方法
下面的方法,将会反射调用User类的一个最简单的方法:
publicintGetUserID(stringIdString){}
该方法只有一个一个参数和一个简单的返回值,下面是C++/CLI的反射调用代码:
intGetUserID(String^iDstring) { MethodInfo^method=this->dotnetObject->GetType()->GetMethod("GetUserID",BindingFlags::Public|BindingFlags::Instance); Func<String^,int>^fun=(Func<String^,int>^)Delegate::CreateDelegate(Func<String^,int>::typeid,this->dotnetObject,method); intresult=fun(iDstring); returnresult; }
注意这里创建了一个Func<String,int>的委托方法,使用委托能够简化我们的反射调用并且有时候还能够提高效率,在这段代码中,有1个要注意的地方:
Func<String^,int>::typeid
这是C++/CLI特殊的语法,表示获取“句柄”类型的类型ID,实际上它的结果就Type对象,等同于C#的
typeof(Func<string,int>)
PS:非常遗憾的是,typeid方式,没法得到下面类型的类型值:
typeof(Func<,>),这给我们在动态构造泛型对象的时候造成了很大的困惑。
再看一个简单方法的反射:
DateTimeGetUserBirthday(intuserId) { MethodInfo^method=dotnetObject->GetType()->GetMethod("GetUserBirthday",BindingFlags::Public|BindingFlags::Instance); Func<int,DateTime>^fun=(Func<int,DateTime>^)Delegate::CreateDelegate(Func<int,DateTime>::typeid,this->dotnetObject,method); DateTimeresult=fun(userId); returnresult; }
注意:由于DateTime是值类型,因此在进行类型申明的时候,不需要加^符号,仅需要对Func委托加上^句柄标记。
有了这2个简单的方法,我们来看看如何调用这个.NET方法“代理类”:
NetLibProxy::UserProxy^proxy=gcnewNetLibProxy::UserProxy("..\\NetLib\\bin\\Debug\\NetLib.dll"); intresult=proxy->GetUserID("123456"); DateTimedate=proxy->GetUserBirthday(result); System::Console::WriteLine("C++/CLI.NetProxyClassCallTestResult:\r\nUserID={0},\r\nBirthday={1}", result,date.ToShortDateString());
OK,第一个C++/CLI代码调用成功,而且还是反射调用的,心情小激动一下。
有关C++/CLI的反射,委托的详细资料,可以参考MSDN的介绍:
https://msdn.microsoft.com/zh-cn/library/2x8kf7zx.aspx使用C++互操作(隐式PInvoke)
https://msdn.microsoft.com/zh-CN/library/213x8e7w.aspx泛型委托
在下一篇,我们将继续探究C++/CLI反射调用.NET中可能遇到"深坑",因此仅打算吧本篇文章作为一个“入门”,免得大家心生恐惧,错过了挑战艰险的机会。
以上所述是小编给大家介绍的在C++中反射调用.NET的方法(一),希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!