程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 在C++中反射調用.NET的辦法(三)

在C++中反射調用.NET的辦法(三)

編輯:關於C++

在C++中反射調用.NET的辦法(三)。本站提示廣大學習愛好者:(在C++中反射調用.NET的辦法(三))文章只能為提供參考,不一定能成為您想要的結果。以下是在C++中反射調用.NET的辦法(三)正文


在.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代碼通訊的效率。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved