在.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; }</div>
该方法没有什么复杂业务逻辑,就是将传递进来的参数给DTO对象,创建包含10个这样的对象的列表并返回而已。
对于 GetUsers方法,我们可以创建下面的委托方法来绑定:
Func<String, IEnumerable> fun;</div>
注意这里使用的是非泛型的 IEnumerable接口,在C++需要使用下面这个命名空间:
using namespace System::Collections;</div>
那么为何不能使用泛型集合呢?
using namespace System::Collections::Generic;</div>
因为在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; }</div>
在C++中,常常使用 list来表示一个列表数据,例如上面方法中的代码:
std::list<CppUserInfo> cppResult;</div>
为此C++需要包含以下头文件:
#include <list></div>
要将一个对象添加到列表结尾,像下面这样调用即可:
cppResult.push_back(user);</div>
在上一篇中已经讲述了如何从.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); }</div>
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; }</div>
方法非常简单,没有什么业务逻辑,接受一个列表接口的数据,然后返回一个布尔值。
在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];</div>
注意上面的代码中使用了C++/CLI的数组类型 array<Type^>^ ,而不是C++标准库的数组,因此不要引用下面的命名空间:
using namespace std;</div>
否则VS会提示数组定义缺少参数。
创建泛型List实例
我们使用List来做集合对象,在C#中,我们可以通过下面的方式得到List泛型的类型,然后进一步创建泛型对象实例:
Type t= typeof(List<>);</div>
但是,对应的C++/CLI写法却无法通过编译:
Type^ t=List<>::typeid;</div>
VS总是提示List缺少类型参数,不过像下面这样子是可以的:
Type^ t2= List<IUserInfo>::typeid;</div>
但是IUserInfo 类型正是我们要动态反射的,事先并不知道,所以一时不知道在C++/CLI中如何构建List泛型的具体实例,MS你不能这么坑好么?
既然无法直接解决,只好曲线救国了,通过类型名字,来创建类型:
String^ listTypeName = System::String::Format("System.Collections.Generic.List`1[{0}]", interfaceType->FullName);</div>
可惜,这种方式不成功,只好一步步来了,先创建基本的List泛型类型:
String^ listTypeName = "System.Collections.Generic.List`1"; Type^ listType = System::Type::GetType(listTypeName);</div>
成功,在此基础上,创建真正的泛型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; }</div>
在方法 CreateGenericListType得到只是一个泛型List的类型,但我们并不知道这个List具体的形参类型,所以这个泛型List还是无法直接使用,幸好,泛型List也是继承自非泛型的IList接口的,所以在 CreateGeneri