在C++中反射調(diào)用.NET的方法(三)
在.NET與C++之間傳輸集合數(shù)據(jù)
上一篇《在C++中反射調(diào)用.NET(二)》中,我們嘗試了反射調(diào)用一個(gè)返回DTO對象的.NET方法,今天來看看如何在.NET與C++之間傳輸集合數(shù)據(jù)。
使用非泛型集合的委托方法
先看看.NET類中的一個(gè)返回列表數(shù)據(jù)的方法:
//返回List或者數(shù)組,不影響 C++調(diào)用 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; }
該方法沒有什么復(fù)雜業(yè)務(wù)邏輯,就是將傳遞進(jìn)來的參數(shù)給DTO對象,創(chuàng)建包含10個(gè)這樣的對象的列表并返回而已。
對于 GetUsers方法,我們可以創(chuàng)建下面的委托方法來綁定:
Func<String, IEnumerable> fun;
注意這里使用的是非泛型的 IEnumerable接口,在C++需要使用下面這個(gè)命名空間:
using namespace System::Collections;
那么為何不能使用泛型集合呢?
using namespace System::Collections::Generic;
因?yàn)樵贑++端,沒有直接引用用戶項(xiàng)目的.NET程序集,并不知道泛型集合類型的具體類型,IUserInfo這個(gè)接口無法直接訪問,好在IEnumerable<T>也是繼承 IEnumerable 的,所以可以當(dāng)做非泛型對象在C++中訪問,因此創(chuàng)建上面的委托方法是可行的。
C++中的列表對象list
下面看看完整的C++/CLI反射調(diào)用的代碼:
std::list<CppUserInfo> GetUsers(String^ likeName) { //調(diào)用.NET方法,得到結(jié)果 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來表示一個(gè)列表數(shù)據(jù),例如上面方法中的代碼:
std::list<CppUserInfo> cppResult;
為此C++需要包含以下頭文件:
#include <list>
要將一個(gè)對象添加到列表結(jié)尾,像下面這樣調(diào)用即可:
cppResult.push_back(user);
在上一篇中已經(jīng)講述了如何從.NET對象轉(zhuǎn)換給C++本地結(jié)構(gòu)體,所以這個(gè)轉(zhuǎn)換代碼可以直接拿來用,綜合起來,要從.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++傳遞集合數(shù)據(jù)給.NET
前面講了從.NET反射調(diào)用獲得一個(gè)集合,看起來比較容易,但是從C++反射調(diào)用時(shí)候傳遞一個(gè)集合就不容易了。注意,這里傳遞的還是.NET的集合,所以這里需要做3件事情:
1,首先構(gòu)建一個(gè).NET集合對象;
2,轉(zhuǎn)換C++本機(jī)結(jié)構(gòu)數(shù)據(jù)到.NET集合元素;
3,反射調(diào)用.NET方法,傳遞數(shù)據(jù)過去。
先看要反射調(diào)用的.NET方法定義:
public bool SaveUsers(IList<IUserInfo> users) { UserDb.AddRange(users); return true; }
方法非常簡單,沒有什么業(yè)務(wù)邏輯,接受一個(gè)列表接口的數(shù)據(jù),然后返回一個(gè)布爾值。
在C++端看來,SaveUsers方法的參數(shù)對象是一個(gè)泛型集合,但是具體是什么對象并不知道,所以需要反射出泛型集合的類型,同時(shí)還需要構(gòu)建這樣一個(gè)泛型集合對象實(shí)例。
在本例中,要得到IUserInfo 這個(gè)泛型集合的類型,可以通過下面的代碼:
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的數(shù)組類型 array<Type^>^ ,而不是C++標(biāo)準(zhǔn)庫的數(shù)組,因此不要引用下面的命名空間:
using namespace std;
否則VS會(huì)提示數(shù)組定義缺少參數(shù)。
創(chuàng)建泛型List實(shí)例
我們使用List來做集合對象,在C#中,我們可以通過下面的方式得到List泛型的類型,然后進(jìn)一步創(chuàng)建泛型對象實(shí)例:
Type t= typeof(List<>);
但是,對應(yīng)的C++/CLI寫法卻無法通過編譯:
Type^ t=List<>::typeid;
VS總是提示List缺少類型參數(shù),不過像下面這樣子是可以的:
Type^ t2= List<IUserInfo>::typeid;
但是IUserInfo 類型正是我們要?jiǎng)討B(tài)反射的,事先并不知道,所以一時(shí)不知道在C++/CLI中如何構(gòu)建List泛型的具體實(shí)例,MS你不能這么坑好么?
既然無法直接解決,只好曲線救國了,通過類型名字,來創(chuàng)建類型:
String^ listTypeName = System::String::Format("System.Collections.Generic.List`1[{0}]", interfaceType->FullName);
可惜,這種方式不成功,只好一步步來了,先創(chuàng)建基本的List泛型類型:
String^ listTypeName = "System.Collections.Generic.List`1"; Type^ listType = System::Type::GetType(listTypeName);
成功,在此基礎(chǔ)上,創(chuàng)建真正的泛型List對象實(shí)例就可以了,完整代碼如下:
static Type^ CreateGenericListType(Type^ interfaceType) { //直接這樣創(chuàng)建泛型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得到只是一個(gè)泛型List的類型,但我們并不知道這個(gè)List具體的形參類型,所以這個(gè)泛型List還是無法直接使用,幸好,泛型List也是繼承自非泛型的IList接口的,所以在 CreateGenericList 方法中將泛型List對象轉(zhuǎn)換成IList接口對象,之后就可以愉快的使用List對象了。
IList^ realList = CreateGenericList(interfaceType);
realList->Add(CurrEntity);//CurrEntity 是interfaceType 類型的動(dòng)態(tài)實(shí)體類
反射靜態(tài)方法
在上一篇中,我們在一個(gè).NET方法中通過接口動(dòng)態(tài)創(chuàng)建實(shí)體類,用的是下面的方式:
IUserInfo userinfo= EntityBuilder.CreateEntity<IUserInfo>();
CreateEntity是EntityBuilder的靜態(tài)方法,現(xiàn)在我們需要在C++/CLI中,反射調(diào)用此方法。
為什么要反射創(chuàng)建實(shí)體類?
因?yàn)镃reateGenericList(interfaceType) 創(chuàng)建的是一個(gè)泛型List對象,要求它的成員是一個(gè)實(shí)體類。
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; }
注意,由于是反射調(diào)用靜態(tài)方法,并且調(diào)用方法時(shí)候并不需要參數(shù),所以Invoke方法的參數(shù)為空。
在C++/CLI中,用nullptr表示空引用,跟C#的null作用一樣。
反射調(diào)用索引器
SOD實(shí)體類可以通過索引器來訪問對象屬性,例如下面的C#代碼:
int id=(int)CurrEntity["ID"]; CurrEntity["Name"]="張三"; string name=(string)CurrEntity["Name"];//張三
下面,我們研究如何通過索引器來給實(shí)體類的屬性賦值:
我們定義一個(gè) 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); //參數(shù)類型為 Object的委托,可能沒有性能優(yōu)勢,反而更慢。 //this->idxAction(propName, value); }
對索引器的訪問,實(shí)際上就是調(diào)用類的 set_Item 方法,VS編譯器會(huì)給包含索引器的對象生成這個(gè)方法,一般來說我們會(huì)對要反射調(diào)用的方法創(chuàng)建一個(gè)委托,但是實(shí)驗(yàn)證明,對索引器使用委托方法調(diào)用,反而效率不如直接反射調(diào)用,即下面的代碼:
void SetPropertyValue(Object^ entity, MethodInfo^ propMethod, String^ propName, Object^ value) { array<Object^>^ paraArr = gcnew array<Object^>{propName, value}; propMethod->Invoke(entity, paraArr); }
注:C++/CLI 的數(shù)組,也可以通過{ } 進(jìn)行初始化。
一切準(zhǔn)備就緒,下面可以通過以下步驟提交集合數(shù)據(jù)給.NET方法了:
1,反射.NET方法,獲取參數(shù)的泛型形參類型;
2,創(chuàng)建此泛型形參的泛型List對象實(shí)例;
3,遍歷C++集合(列表list),將結(jié)構(gòu)數(shù)據(jù)賦值給動(dòng)態(tài)創(chuàng)建的實(shí)體類對象;
4,添加動(dòng)態(tài)實(shí)體類到泛型List對象集合內(nèi);
5,反射調(diào)用.NET方法,提交數(shù)據(jù)。
//示例1:直接調(diào)用.NET強(qiáng)類型的參數(shù)方法 //僅僅適用于有一個(gè)參數(shù)的情況并且要求是泛型類型參數(shù) 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; }
使用弱類型集合傳輸數(shù)據(jù)
當(dāng)委托遇到協(xié)變和逆變
看看下面兩個(gè)委托方法,哪個(gè)可以綁定到本文說的這個(gè).NET方法:
bool SaveUsers(IList<IUserInfo> users){ } Func<List<IUserInfo>,bool> fun; Func<List<Object>,bool> fun2;
很明顯,委托方法 fun2不能綁定,因?yàn)閰?shù)是 in 的,不是方法out的,所以調(diào)用的參數(shù)類型不能使用派生程度更小的類型;
再看看下面這種情況:
List<IUserInfo> GetUsers(string likeName){ } Func<string,IEnumerable<IUserInfo>> fun; Func<string,IEnumerable> fun2;
這里,fun,fun2都可以綁定到方法上,因?yàn)榉盒头椒ǖ男螀⒆鳛榉祷刂?,是out的,可以使用派生程度更小的類型。
這是不是很熟悉的泛型類型的 協(xié)變和逆變?
我們知道,反射的時(shí)候,利用委托綁定要反射的方法,能夠大大提高方法的調(diào)用效率,所以對于我們的方法參數(shù),如果調(diào)用的時(shí)候無法獲知具體的類型,從而無法正確構(gòu)造合適的委托方法,不如退而求其次,讓被調(diào)用的方法參數(shù)采用弱類型方式,這樣就可以構(gòu)造對應(yīng)的委托方法了。
因此,對我們.NET方法中的 SaveUsers 進(jìn)行改造:
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()); }
這里增加一個(gè)方法 SaveUsers2,它采用IEnumerable<Object> ,而不是更為具體的 IList<IUserInfo>,那么采用下面的方式構(gòu)造方法 SaveUsers2 對應(yīng)的委托方法就可以了:
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);
這樣要構(gòu)造一個(gè)泛型List就不必像之前的方法那么麻煩了:
System::Collections::Generic::List<Object^>^ list = gcnew System::Collections::Generic::List<Object^>;
反射調(diào)用SaveUser2完整的代碼如下:
//示例2:調(diào)用.NET弱類型的參數(shù)方法,以便通過委托方法調(diào)用 //構(gòu)建委托方法比較容易,適用于參數(shù)數(shù)量多于1個(gè)的情況, 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 反射調(diào)用兩種方案(直接反射調(diào)用,委托方法調(diào)用)的效率,我們循環(huán)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);
不調(diào)試,直接執(zhí)行:
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程序端,我們使用了弱類型的泛型集合,綜合起來還是反射+委托方法執(zhí)行,效率要高。
所以如果你能夠適當(dāng)對要調(diào)用的.NET方法進(jìn)行封裝,那么可采用使用弱類型集合傳輸數(shù)據(jù)的方案,否則,就在C++/CLI端多寫2行代碼,使用強(qiáng)類型傳輸數(shù)據(jù)的方案。
與.NET直接調(diào)用和反射的性能比較
在本篇的方案中,都是C++反射來調(diào)用.NET方法的,如果都是在.NET應(yīng)用程序中直接調(diào)用或者反射.NET方法,性能差距有多少呢?
我們模擬文中 C++/CLI的UserProxy,寫一個(gè).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
然后同樣循環(huán)1000此調(diào)用,直接執(zhí)行,看執(zhí)行結(jié)果:
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 平臺(tái)內(nèi)調(diào)用,反射+委托的性能是接近于直接方法調(diào)用的。
綜合對比,C++/CLI中反射調(diào)用.NET,比起在.NET平臺(tái)內(nèi)部反射調(diào)用,性能沒有很大的差距,所以C++/CLI中反射調(diào)用.NET是一個(gè)可行的方案。
總結(jié)
C++/CLI是一種很好的混合編寫本機(jī)代碼與.NET托管代碼的技術(shù),使用它反射調(diào)用.NET方法也是一種可行的方案,結(jié)合PDF.NET SOD框架的實(shí)體類特征,可以更加方便的簡化C++/CLI反射代碼的編寫并且提高C++代碼與.NET代碼通信的效率。
相關(guān)文章
詳解C++的JSON靜態(tài)鏈接庫JsonCpp的使用方法
這篇文章主要介紹了C++的JSON靜態(tài)鏈接庫JsonCpp的使用方法,演示了使用JsonCpp生成和解析JSON的方法,以及C++通過JSON方式的socket通信示例,需要的朋友可以參考下2016-03-03C 創(chuàng)建鏈表并將信息存儲(chǔ)在二進(jìn)制文件中讀取的實(shí)例代碼
C 創(chuàng)建鏈表并將信息存儲(chǔ)在二進(jìn)制文件中讀取的實(shí)例代碼,需要的朋友可以參考一下2013-03-03opencv3/C++ 實(shí)現(xiàn)SURF特征檢測
今天小編就為大家分享一篇opencv3/C++ 實(shí)現(xiàn)SURF特征檢測,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12include包含頭文件的語句中,雙引號(hào)和尖括號(hào)的區(qū)別(詳解)
下面小編就為大家?guī)硪黄猧nclude包含頭文件的語句中,雙引號(hào)和尖括號(hào)的區(qū)別(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07VisualStudio2022配置opencv的實(shí)現(xiàn)
本文主要介紹了VisualStudio2022配置opencv的實(shí)現(xiàn),文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06