zoukankan      html  css  js  c++  java
  • 在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代码通信的效率。

    (全文完)

  • 相关阅读:
    LeetCode 382. Linked List Random Node
    LeetCode 398. Random Pick Index
    LeetCode 1002. Find Common Characters
    LeetCode 498. Diagonal Traverse
    LeetCode 825. Friends Of Appropriate Ages
    LeetCode 824. Goat Latin
    LeetCode 896. Monotonic Array
    LeetCode 987. Vertical Order Traversal of a Binary Tree
    LeetCode 689. Maximum Sum of 3 Non-Overlapping Subarrays
    LeetCode 636. Exclusive Time of Functions
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/6364555.html
Copyright © 2011-2022 走看看