在C++中反射调用.NET的方法(二)
反射调用返回复杂对象的.NET方法
定义数据接口
上一篇在C++中反射调用.NET(一)中,我们简单的介绍了如何使用C++/CLI并且初步使用了反射调用.NET程序集的简单方法,今天我们看看如何在C++与.NET程序集之间传递复杂对象。
先看看.NET程序集的一个返回对象的方法:
publicIUserInfoGetUserByID(intuserId) { IUserInfouserinfo=EntityBuilder.CreateEntity<IUserInfo>(); userinfo.ID=userId; userinfo.Name="姓名_"+userId; userinfo.Birthday=newDateTime(1980,1,1); returnuserinfo; }
其中IUserInfo是一个用户信息接口:
usingSystem; namespaceNetLib { publicinterfaceIUserInfo { DateTimeBirthday{get;set;} intID{get;set;} stringName{get;set;} } }
接口内容很简单,有int,string,DateTime三种类型的属性,所以可以把它当做.NET与C++传递数据的DTO对象接口。
在方法GetUserByID中,有一行代码:
IUserInfouserinfo=EntityBuilder.CreateEntity<IUserInfo>();
EntityBuilder对象是PDF.NETSOD框架中的一个实体构造器,调用CreateEntity方法可以根据一个接口创建一个动态实体类对象,通过这种方式,我们可以不用去关心实体类的构造细节,仅仅关心方法调用的数据接口。在后面的示例中,我们都会通过这种接口对象的方式来传递数据。
绑定委托方法
下面我们来看看如何在C++/CLI中反射调用GetUserByID这个方法。
虽然方法返回的是IUserInfo,但是对于我们的C++程序端来说,它并不知道IUserInfo这个接口对象,因为此接口没有在C++程序端定义,C++程序也没用引用它所在的.NET程序集,所以我们在反射调用GetUserByID方法的时候,只能使用“弱类型”的Object,幸运的是我们调用的是返回值,而不是参数(反过来就不行,后面会有介绍),创建下面的委托对象是合法的:
Func<int,Object>fun;
详细的C++/CLI反射代码如下:
CppUserInfoGetUserByID(intuserId) { //调用.NET方法,得到结果 MethodInfo^method=dotnetObject->GetType()->GetMethod("GetUserByID",BindingFlags::Public|BindingFlags::Instance); Func<int,Object^>^fun=(Func<int,Object^>^)Delegate::CreateDelegate(Func<int,Object^>::typeid,this->dotnetObject,method); Object^result=fun(userId); //转换托管类型数据到本机结构体 Func<String^,Object^>^entityProp=EntityHelper::EntityCallDelegate(result); CppUserInfouser; user.ID=(int)entityProp("ID"); user.Name=(String^)entityProp("Name");//MarshalString((String^)entityProp("Name")); user.Birthday=Convert2CppDateTime((DateTime^)entityProp("Birthday")); returnuser; }
在上面的代码中,通过委托方法调用:
Object^result=fun(userId);
使用SODDTO对象
我们得到了.NET程序集的方法返回的DTO对象,但是如何取出它的数据赋值给我们的C++本机代码呢?
所以这里涉及到2个问题:
1,从Object对象取出数据;
2,将数据转换并且赋值给C++本地数据结构
对于第一个问题,我们可以反射DTO对象的属性,然后跟本地数据接口一一对应,但是,本来我们已经在反射调用方法了,再来一次反射事情就复杂了。
幸好,我们的DTO接口对象它是一个动态创建的SOD实体类对象,由于SOD实体类有类似“字典”的功能,可以通过相关方法进行访问。
实体类基类的一个方法定义:
publicobjectPropertyList(stringpropertyFieldName)
我们反射此方法并且绑定一个委托对象来调用它:
staticFunc<String^,Object^>^EntityCallDelegate(Object^entity) { //实体类基类的一个方法定义: //publicobjectPropertyList(stringpropertyFieldName) Type^base=entity->GetType()->BaseType; MethodInfo^methodEntity=base->GetMethod("PropertyList",BindingFlags::Public|BindingFlags::Instance); Func<String^,Object^>^funEntity=(Func<String^,Object^>^)Delegate::CreateDelegate(Func<String^,Object^>::typeid, entity,methodEntity); //示例String^result=(String^)funEntity("Name"); returnfunEntity; }
然后,就能像下面这样使用了:
Func<String^,Object^>^entityProp=EntityHelper::EntityCallDelegate(result); intid=(int)entityProp("ID");
将.NET对象转换到C++结构体
在示例中,我们定义了一个CppUserInfo结构体:
structCppUserInfo { intID; //wstringName; CStringName; tmBirthday; };
托管字符串与本机字符串
这个结构体跟C#版本的接口IUserInfo对应,但是结构体成员有几个需要注意的地方:
CStringName;
字符串类型的“名字”成员,要在C++中使用字符串类型,必须在C++文件中包含下面的头文件:
如果不是MFC应用程序,包含下面这个:
#include<atlstr.h>
否则,需要包含这个头文件:
#include<cstringt.h>
如果不是使用CString,而是wstring,那么需要定义一个方法来实现托管字符串到本机字符串的转换:
// //要使用下面的方法,请先#include<string> // staticwstringMarshalString(String^s){ wstringos; constwchar_t*chars= (constwchar_t*)(Marshal::StringToHGlobalUni(s)).ToPointer(); os=chars; Marshal::FreeHGlobal(IntPtr((void*)chars)); returnos; }
上面的方法申明了一个wchar_t*类型的指针,在方法结尾必须释放此指针占用的内存,所以这种形式的转换还是比较麻烦。
有关托管字符串跟C++本机字符串的转换。
托管日期与本机日期数据
在C++中表示日期的结构体是tm,但是需要注意的是tm的year部分仅能够表示与1900的差值,所以我们可以写下面2个方法来简单的转换:
statictmConvert2CppDateTime(DateTime^dt) { tmresult; result.tm_year=dt->Year-1900; result.tm_mon=dt->Month; result.tm_wday=dt->Day; returnresult; } staticDateTime^Covert2NetDateTime(tmcppDate) { returngcnewDateTime( cppDate.tm_year+1900, cppDate.tm_mon, cppDate.tm_wday ); }
有了字符串跟日期类型的.NET与C++的相互转换,基本上就能够使用.NET的DTO对象了,因为其它数字类型只要类型兼容,是可以直接使用的,比如int类型。
转换到本机结构体
下面再回来看看GetUserByID方法内的对象数据转换部分:
//转换托管类型数据到本机结构体 Func<String^,Object^>^entityProp=EntityHelper::EntityCallDelegate(result); CppUserInfouser; user.ID=(int)entityProp("ID"); user.Name=(String^)entityProp("Name");//MarshalString((String^)entityProp("Name")); user.Birthday=Convert2CppDateTime((DateTime^)entityProp("Birthday"));
现在再看看,采用类似“字典”访问方式的SODDTO对象,给C++本地结构体转换赋值数据,就很方便了,这也是本篇选择SOD框架作为C++与.NET通信的原因了。
为何不使用序列化的问题
在进行分布式跨平台调用的时候,序列化常常作为一个有效手段被大量使用,但是我们的应用有几个特点:
1,没有分布式,在进程内进行不同语言平台调用;
2,不知道反序列化的类型,因为C++没有直接引用任何.NET框架自身之外的.NET程序集;
3,序列化需要使用反射,而我们本来已经在反射了,会加重负担;
除此之外,使用序列化还会有额外的工作:
4,使用序列化会要求被调用端进行额外的封装;
5,双方需要制定通用的通信协议,并且定制序列化过程,比如常见RPC框架约定的序列化协议
所以,经过仔细考虑后,放弃了使用序列化方式来进行C++与.NET进行进程内通信的想法。
以上所述是小编给大家介绍的在C++中反射调用.NET的方法(二),希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!