在C++中反射调用.NET的方法(三)

所属分类: 软件编程 / C 语言 阅读数: 83
收藏 0 赞 0 分享

在.NET与C++之间传输集合数据

上一篇《在C++中反射调用.NET(二)》中,我们尝试了反射调用一个返回DTO对象的.NET方法,今天来看看如何在.NET与C++之间传输集合数据。

使用非泛型集合的委托方法

先看看.NET类中的一个返回列表数据的方法:

//返回List或者数组,不影响 C++调用
 public List<IUserInfo> GetUsers(string likeName)
 {
 List<IUserInfo> users = new List<NetLib.IUserInfo>();
 for (int i = 0; i < 10; i++)
 {
 IUserInfo userinfo = GetUserByID(i);
 userinfo.Name += likeName;
 users.Add(userinfo);
 }
 //return users.ToArray();
 return users;
 }
 public IUserInfo GetUserByID(int userId)
 {
 IUserInfo userinfo= EntityBuilder.CreateEntity<IUserInfo>();
 userinfo.ID = userId;
 userinfo.Name = "姓名_" + userId;
 userinfo.Birthday = new DateTime(1980, 1, 1);
 return userinfo;
 }

该方法没有什么复杂业务逻辑,就是将传递进来的参数给DTO对象,创建包含10个这样的对象的列表并返回而已。

对于 GetUsers方法,我们可以创建下面的委托方法来绑定:

Func<String, IEnumerable> fun;

注意这里使用的是非泛型的 IEnumerable接口,在C++需要使用下面这个命名空间:

using namespace System::Collections;

那么为何不能使用泛型集合呢?

using namespace System::Collections::Generic;

因为在C++端,没有直接引用用户项目的.NET程序集,并不知道泛型集合类型的具体类型,IUserInfo这个接口无法直接访问,好在IEnumerable<T>也是继承 IEnumerable 的,所以可以当做非泛型对象在C++中访问,因此创建上面的委托方法是可行的。

C++中的列表对象list

下面看看完整的C++/CLI反射调用的代码:

 std::list<CppUserInfo> GetUsers(String^ likeName)
 {
 //调用.NET方法,得到结果
 MethodInfo^ method = dotnetObject->GetType()->GetMethod("GetUsers", BindingFlags::Public | BindingFlags::Instance);
 Func<String^, IEnumerable^>^ fun = (Func<String^, IEnumerable^>^)Delegate::CreateDelegate(Func<String^, IEnumerable^>::typeid, 
 this->dotnetObject, method);
 IEnumerable^ result = fun(likeName);
 std::list<CppUserInfo> cppResult;
 for each (Object^ item in result)
 {
 Func<String^, Object^>^ entityProp = EntityHelper::EntityCallDelegate(item);
 CppUserInfo user;
 user.ID = (int)entityProp("ID");
 user.Name = (String^)entityProp("Name");
 user.Birthday = Convert2CppDateTime((DateTime^)entityProp("Birthday"));
 cppResult.push_back(user);
 }
 return cppResult;
 }

在C++中,常常使用 list来表示一个列表数据,例如上面方法中的代码:

std::list<CppUserInfo> cppResult;

为此C++需要包含以下头文件:

#include <list>

 要将一个对象添加到列表结尾,像下面这样调用即可:

cppResult.push_back(user);

在上一篇中已经讲述了如何从.NET对象转换给C++本地结构体,所以这个转换代码可以直接拿来用,综合起来,要从.NET集合得到C++的列表对象,像下面这样使用:

std::list<CppUserInfo> cppResult;
 for each (Object^ item in result)
 {
 Func<String^, Object^>^ entityProp = EntityHelper::EntityCallDelegate(item);
 CppUserInfo user;
 user.ID = (int)entityProp("ID");
 user.Name = (String^)entityProp("Name");
 user.Birthday = Convert2CppDateTime((DateTime^)entityProp("Birthday"));
 cppResult.push_back(user);
 }

 C++传递集合数据给.NET

前面讲了从.NET反射调用获得一个集合,看起来比较容易,但是从C++反射调用时候传递一个集合就不容易了。注意,这里传递的还是.NET的集合,所以这里需要做3件事情:

1,首先构建一个.NET集合对象;

2,转换C++本机结构数据到.NET集合元素;

3,反射调用.NET方法,传递数据过去。

先看要反射调用的.NET方法定义:

 public bool SaveUsers(IList<IUserInfo> users)
 {
 UserDb.AddRange(users);
 return true;
 }

方法非常简单,没有什么业务逻辑,接受一个列表接口的数据,然后返回一个布尔值。

在C++端看来,SaveUsers方法的参数对象是一个泛型集合,但是具体是什么对象并不知道,所以需要反射出泛型集合的类型,同时还需要构建这样一个泛型集合对象实例。

在本例中,要得到IUserInfo 这个泛型集合的类型,可以通过下面的代码:

MethodInfo^ method = dotnetObject->GetType()->GetMethod("SaveUsers", BindingFlags::Public | BindingFlags::Instance);
array<ParameterInfo^>^ pars = method->GetParameters();
Type^ paraType= pars[0]->ParameterType;
Type^ interfaceType = paraType->GetGenericArguments()[0];

注意上面的代码中使用了C++/CLI的数组类型 array<Type^>^ ,而不是C++标准库的数组,因此不要引用下面的命名空间:

using namespace std;

否则VS会提示数组定义缺少参数。

创建泛型List实例

我们使用List来做集合对象,在C#中,我们可以通过下面的方式得到List泛型的类型,然后进一步创建泛型对象实例:

Type t= typeof(List<>);

但是,对应的C++/CLI写法却无法通过编译:

Type^ t=List<>::typeid;

VS总是提示List缺少类型参数,不过像下面这样子是可以的:

Type^ t2= List<IUserInfo>::typeid;

但是IUserInfo 类型正是我们要动态反射的,事先并不知道,所以一时不知道在C++/CLI中如何构建List泛型的具体实例,MS你不能这么坑好么?

既然无法直接解决,只好曲线救国了,通过类型名字,来创建类型:

 String^ listTypeName = System::String::Format("System.Collections.Generic.List`1[{0}]", interfaceType->FullName);

可惜,这种方式不成功,只好一步步来了,先创建基本的List泛型类型:

 String^ listTypeName = "System.Collections.Generic.List`1";
 Type^ listType = System::Type::GetType(listTypeName);

成功,在此基础上,创建真正的泛型List对象实例就可以了,完整代码如下:

static Type^ CreateGenericListType(Type^ interfaceType)
 {
 //直接这样创建泛型List不成功:
 // String^ listTypeName = System::String::Format("System.Collections.Generic.List`1[{0}]", interfaceType->FullName);
 String^ listTypeName = "System.Collections.Generic.List`1";
 Type^ listType = System::Type::GetType(listTypeName);
 Type^ generListType = listType->MakeGenericType(interfaceType);
 return generListType;
 }
 static IList^ CreateGenericList(Type^ interfaceType)
 {
 Type^ generListType = CreateGenericListType(interfaceType);
 Object^ listObj = System::Activator::CreateInstance(generListType, nullptr);
 IList^ realList = (IList^)listObj;
 return realList;
 }

在方法 CreateGenericListType得到只是一个泛型List的类型,但我们并不知道这个List具体的形参类型,所以这个泛型List还是无法直接使用,幸好,泛型List也是继承自非泛型的IList接口的,所以在 CreateGenericList 方法中将泛型List对象转换成IList接口对象,之后就可以愉快的使用List对象了。

IList^ realList = CreateGenericList(interfaceType);

realList->Add(CurrEntity);//CurrEntity 是interfaceType 类型的动态实体类

反射静态方法

在上一篇中,我们在一个.NET方法中通过接口动态创建实体类,用的是下面的方式:

IUserInfo userinfo= EntityBuilder.CreateEntity<IUserInfo>();

CreateEntity是EntityBuilder的静态方法,现在我们需要在C++/CLI中,反射调用此方法。

为什么要反射创建实体类?

因为CreateGenericList(interfaceType) 创建的是一个泛型List对象,要求它的成员是一个实体类。

Object^ CreateEntityFromInterface(Type^ interfaceType)
 {
  MethodInfo^ method = this->entityBuilderType->GetMethod("CreateEntity", BindingFlags::Public | BindingFlags::Static);
  MethodInfo^ genMethod = method->MakeGenericMethod(interfaceType);
  Object^ entity = genMethod->Invoke(nullptr, nullptr);
  this->CurrEntity = entity;
  return entity;
 }

注意,由于是反射调用静态方法,并且调用方法时候并不需要参数,所以Invoke方法的参数为空。
在C++/CLI中,用nullptr表示空引用,跟C#的null作用一样。

反射调用索引器

SOD实体类可以通过索引器来访问对象属性,例如下面的C#代码:

int id=(int)CurrEntity["ID"];
CurrEntity["Name"]="张三";
string name=(string)CurrEntity["Name"];//张三

下面,我们研究如何通过索引器来给实体类的属性赋值:

我们定义一个 EntityHelper的C++/CLI类,在中间添加下面的代码:

private:
 Type^ entityBuilderType;
 MethodInfo^ mset; 
 Object^ _CurrEntity;
 //Action<String^, Object^>^ idxAction;
 void SetPropertyValue(Object^ entity, MethodInfo^ propMethod, String^ propName, Object^ value)
 {
  array<Object^>^ paraArr = gcnew array<Object^>{propName, value};
  propMethod->Invoke(entity, paraArr);
 }
public:
void set(Object^ value)
{
 this->mset = _CurrEntity->GetType()->GetMethod("set_Item", BindingFlags::Public | BindingFlags::Instance);
 //this->idxAction= (Action<String^, Object^>^)Delegate::CreateDelegate(Action<String^, Object^>::typeid, _CurrEntity, this->mset);
}
void SetPropertyValue(String^ propName, Object^ value)
{
 this->SetPropertyValue(this->CurrEntity, this->mset, propName, value);
 //参数类型为 Object的委托,可能没有性能优势,反而更慢。
 //this->idxAction(propName, value);
}

对索引器的访问,实际上就是调用类的 set_Item 方法,VS编译器会给包含索引器的对象生成这个方法,一般来说我们会对要反射调用的方法创建一个委托,但是实验证明,对索引器使用委托方法调用,反而效率不如直接反射调用,即下面的代码:

void SetPropertyValue(Object^ entity, MethodInfo^ propMethod, String^ propName, Object^ value)
 {
  array<Object^>^ paraArr = gcnew array<Object^>{propName, value};
  propMethod->Invoke(entity, paraArr);
 }

注:C++/CLI 的数组,也可以通过{ } 进行初始化。

一切准备就绪,下面可以通过以下步骤提交集合数据给.NET方法了:

1,反射.NET方法,获取参数的泛型形参类型;

2,创建此泛型形参的泛型List对象实例;

3,遍历C++集合(列表list),将结构数据赋值给动态创建的实体类对象;

4,添加动态实体类到泛型List对象集合内;

5,反射调用.NET方法,提交数据。

//示例1:直接调用.NET强类型的参数方法
 //仅仅适用于有一个参数的情况并且要求是泛型类型参数
 bool SaveUsers(std::list<CppUserInfo> users)
 {
  MethodInfo^ method = dotnetObject->GetType()->GetMethod("SaveUsers", BindingFlags::Public | BindingFlags::Instance);
  array<ParameterInfo^>^ pars = method->GetParameters();
  Type^ paraType= pars[0]->ParameterType;
  Type^ interfaceType = paraType->GetGenericArguments()[0];
  IList^ realList = CreateGenericList(interfaceType);
  Object^ userObj = helper->CreateEntityFromInterface(interfaceType);
  for each (CppUserInfo user in users)
  {
  helper->CurrEntity = ((ICloneable^)userObj)->Clone();//使用克隆,避免每次反射
  helper->SetPropertyValue("ID", user.ID);
  helper->SetPropertyValue("Name", gcnew String(user.Name));
  helper->SetPropertyValue("Birthday", Covert2NetDateTime(user.Birthday));
  realList->Add(helper->CurrEntity);
  }
  Object^ result= method->Invoke(dotnetObject, gcnew array<Object^>{ realList});
  return (bool)result;
 }

使用弱类型集合传输数据

当委托遇到协变和逆变

看看下面两个委托方法,哪个可以绑定到本文说的这个.NET方法:

bool SaveUsers(IList<IUserInfo> users){ }
Func<List<IUserInfo>,bool> fun;
Func<List<Object>,bool> fun2;

很明显,委托方法 fun2不能绑定,因为参数是 in 的,不是方法out的,所以调用的参数类型不能使用派生程度更小的类型;

再看看下面这种情况:

List<IUserInfo> GetUsers(string likeName){ }
Func<string,IEnumerable<IUserInfo>> fun;
Func<string,IEnumerable> fun2;

这里,fun,fun2都可以绑定到方法上,因为泛型方法的形参作为返回值,是out的,可以使用派生程度更小的类型。

这是不是很熟悉的泛型类型的 协变和逆变?

我们知道,反射的时候,利用委托绑定要反射的方法,能够大大提高方法的调用效率,所以对于我们的方法参数,如果调用的时候无法获知具体的类型,从而无法正确构造合适的委托方法,不如退而求其次,让被调用的方法参数采用弱类型方式,这样就可以构造对应的委托方法了。

因此,对我们.NET方法中的 SaveUsers 进行改造:

public bool SaveUsers(IList<IUserInfo> users)
 {
  UserDb.AddRange(users);
  return true;
 }
 public IUserInfo CreateUserObject()
 {
  return EntityBuilder.CreateEntity<IUserInfo>();
 }
 public bool SaveUsers2(IEnumerable<Object> para)
 {
  var users = from u in para
   select u as IUserInfo;
  return SaveUsers (users.ToList());
 }

这里增加一个方法 SaveUsers2,它采用IEnumerable<Object> ,而不是更为具体的  IList<IUserInfo>,那么采用下面的方式构造方法 SaveUsers2 对应的委托方法就可以了:

MethodInfo^ method = dotnetObject->GetType()->GetMethod("SaveUsers2", BindingFlags::Public | BindingFlags::Instance);
Func<System::Collections::Generic::IEnumerable<Object^>^,bool>^ fun2 = 
  (Func<System::Collections::Generic::IEnumerable<Object^>^, bool>^)Delegate::CreateDelegate(System::Func<Collections::Generic::IEnumerable<Object^>^, bool>::typeid,
  this->dotnetObject, method);

这样要构造一个泛型List就不必像之前的方法那么麻烦了:

System::Collections::Generic::List<Object^>^ list = gcnew System::Collections::Generic::List<Object^>;

反射调用SaveUser2完整的代码如下:

//示例2:调用.NET弱类型的参数方法,以便通过委托方法调用
 //构建委托方法比较容易,适用于参数数量多于1个的情况,
 bool SaveUsers2(std::list<CppUserInfo> users)
 {
  MethodInfo^ method = dotnetObject->GetType()->GetMethod("SaveUsers2", BindingFlags::Public | BindingFlags::Instance);
  Func<System::Collections::Generic::IEnumerable<Object^>^,bool>^ fun2 =
   (Func<System::Collections::Generic::IEnumerable<Object^>^, bool>^)Delegate::CreateDelegate(System::Func<Collections::Generic::IEnumerable<Object^>^, bool>::typeid,
   this->dotnetObject, method);
  Object^ userObj = CreateUserObject();
  System::Collections::Generic::List<Object^>^ list = gcnew System::Collections::Generic::List<Object^>;
  for each (CppUserInfo user in users)
  {
  helper->CurrEntity = ((ICloneable^)userObj)->Clone();//使用克隆,避免每次反射
  helper->SetPropertyValue("ID", user.ID);
  helper->SetPropertyValue("Name", gcnew String(user.Name));
  helper->SetPropertyValue("Birthday", Covert2NetDateTime(user.Birthday));
  list->Add(helper->CurrEntity);
  }
  bool result = fun2(list);
  return result;
 }

性能测试

C++/CLI 反射性能测试

为了测试 C++/CLI 反射调用两种方案(直接反射调用,委托方法调用)的效率,我们循环1000次测试,下面是测试代码:

NetLibProxy::UserProxy^ proxy = gcnew NetLibProxy::UserProxy("..\\NetLib\\bin\\Debug\\NetLib.dll");
std::list<CppUserInfo> list = proxy->GetUsers("张");
 System::Console::WriteLine("C++ Get List data From .NET function,OK.");
 System::Diagnostics::Stopwatch^ sw = gcnew System::Diagnostics::Stopwatch;
 sw->Start();
 for (int i = 0; i<1000; i++)
 proxy->SaveUsers(list);
 sw->Stop();
 System::Console::WriteLine("1,1000 loop,C++ Post List data To .NET function,OK.use time(ms):{0}",sw->ElapsedMilliseconds);
 sw->Restart();
 for(int i=0;i<1000;i++)
 proxy->SaveUsers2(list);
 sw->Stop();
 System::Console::WriteLine("2,1000 loop,C++ Post List data To .NET function,OK..use time(ms):{0}", sw->ElapsedMilliseconds);

不调试,直接执行:

C++ Get List data From .NET function,OK.
1,1000 loop,C++ Post List data To .NET function,OK.use time(ms):65
2,1000 loop,C++ Post List data To .NET function,OK..use time(ms):48

可见,虽然在.NET程序端,我们使用了弱类型的泛型集合,综合起来还是反射+委托方法执行,效率要高。

所以如果你能够适当对要调用的.NET方法进行封装,那么可采用使用弱类型集合传输数据的方案,否则,就在C++/CLI端多写2行代码,使用强类型传输数据的方案。

与.NET直接调用和反射的性能比较

在本篇的方案中,都是C++反射来调用.NET方法的,如果都是在.NET应用程序中直接调用或者反射.NET方法,性能差距有多少呢?

我们模拟文中 C++/CLI的UserProxy,写一个.NET中的 UserProxy:

struct UserStruct
 {
  public int ID;
  public string Name;
  public DateTime Birthday;
 }
 class UserProxy
 {
  User user;
  public UserProxy()
  {
   user = new User();
  }
  public List<UserStruct> GetUsers(string likeName)
  {
   List<UserStruct> result = new List<NetApp.UserStruct>();
   var list = user.GetUsers(likeName);
   foreach (var item in list)
   {
    UserStruct us;
    us.ID = item.ID;
    us.Name = item.Name;
    us.Birthday = item.Birthday;
    result.Add(us);
   }
   return result;
  }
  public bool SaveUsers(IList<UserStruct> users)
  {
   List<IUserInfo> list = new List<IUserInfo>();
   IUserInfo userObj = user.CreateUserObject();
   foreach (var item in users)
   {
    IUserInfo currUser = (IUserInfo)((ICloneable)userObj).Clone();
    currUser.ID = item.ID;
    currUser.Name = item.Name;
    currUser.Birthday = item.Birthday;
    list.Add(currUser);
   }
   bool result = user.SaveUsers(list);
   return result;
  }

  Object CreateUserObject()
  {
   MethodInfo method = user.GetType().GetMethod("CreateUserObject", BindingFlags.Public | BindingFlags.Instance);
   Func<Object> fun = (Func<Object>)Delegate.CreateDelegate(typeof( Func<Object>), user, method);
   return fun();
  }
  //反射+委托
  public bool SaveUsers2(IList<UserStruct> users)
  {
   MethodInfo method = user.GetType().GetMethod("SaveUsers2", BindingFlags.Public | BindingFlags.Instance);
   Func<System.Collections.Generic.IEnumerable<Object>, bool> fun2 = (Func<System.Collections.Generic.IEnumerable<Object>, bool>)Delegate.CreateDelegate(typeof( System.Func<System.Collections.Generic.IEnumerable<Object>, bool>),
    user, method);
   List<IUserInfo> list = new List<IUserInfo>();
   object userObj = CreateUserObject();
   foreach (var item in users)
   {
    IUserInfo currUser = (IUserInfo)((ICloneable)userObj).Clone();
    currUser.ID = item.ID;
    currUser.Name = item.Name;
    currUser.Birthday = item.Birthday;
    list.Add(currUser);
   }
   bool result = fun2(list);
   return result;
  }
}
.Net UserProxy

然后同样循环1000此调用,直接执行,看执行结果:

1,1000 loop,.NET Post List data To .NET function,OK.use time(ms):4
2,1000 loop,.NET Reflection Post List data To .NET function,OK.use time(ms):14

可见,.NET 平台内调用,反射+委托的性能是接近于直接方法调用的。
综合对比,C++/CLI中反射调用.NET,比起在.NET平台内部反射调用,性能没有很大的差距,所以C++/CLI中反射调用.NET是一个可行的方案。

C++/CLI是一种很好的混合编写本机代码与.NET托管代码的技术,使用它反射调用.NET方法也是一种可行的方案,结合PDF.NET SOD框架的实体类特征,可以更加方便的简化C++/CLI反射代码的编写并且提高C++代码与.NET代码通信的效率。

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多