• 在C++中反射调用.NET(一)


    为什么要在C++中调用.NET

    一般情况下,我们常常会在.NET程序中调用C/C++的程序,使用P/Invoke方式进行调用,在编写代码代码的时候,首先要导入DLL文件,然后在根据C/C++的头文件编写特殊的C#平台调用代码,例如像下面这个样子:

     [DllImport("Interop.dll",EntryPoint = "Multiply",CharSet = CharSet.Ansi)]
     static extern int Multiply(int factorA, int factorB);

    详细的过程,可以参考之前我这篇文章:《C#调用C和C++函数的一点区别

    有时候,我们也会有在C++中调用.NET的需求,比如我们在维护一个大型的C++应用程序,它年代久远,现在需要增加一些新功能,而这些功能在.NET中已经有了,只需要调用它即可,如果为了方便想要用.NET重写这个C++应用程序是不太现实的,幸好,C++/CLI提供了一个简便的方案使得可以在C++中直接编写.NET程序,所以C++/CLI代表托管和本地编程的结合,可以在托管代码中直接使用本地代码,也可以反过来,这样结合了C++本地代码的高效性和.NET代码的强大性,看起来是非常有潜力的。

    使用C++/CLI进行.NET编程

    要进行C++/CLI编程,只需要进行下面的步骤:
    1,添加.NET程序集的应用;
    2,修改C++项目属性,配置属性->公共语言运行时支持-公共语言运行时支持(/clr)

    然而,为了保持C++与.NET应用程序的独立性,要求不能将.NET的DLL文件放到C++的应用程序目录下,因此上述步骤1不可行,需要在C++代码中使用反射来调用.NET。
    注意,本文说的C++反射调用,不是对C++自身进行封装的反射功能,而是在C++/CLI代码中反射调用.NET代码,原理上跟你在.NET应用中反射调用另外一个.NET的程序集一个道理。

    首先,我们建立一个名字叫CppNetTest的解决方案,添加3个项目:
    1,CppConsoleTest---一个C++控制台项目,在项目中更改属性支持CLR;
    2,NetApp--一个.NET控制台应用程序,作为对比示例代码,方便编写C++/CLI代码参考;
    3,NetLib--一个.NET类库程序集,它将被1和2项目进行反射调用。

    我们先在NetLib项目写一个简单的.NET 类,这个类的方法内部没有复杂的业务逻辑代码,仅仅用来供反射调用测试:

    namespace NetLib
    {
        public class User
        {
            static List<IUserInfo> UserDb = new List<IUserInfo>();
    
            public int GetUserID(string IdString)
            {
                int result = 0;
                int.TryParse(IdString, out result);
                return result;
            }
    
            public DateTime GetUserBirthday(int userId)
            {
                return new DateTime(1980, 1, 1);
            }
    
            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;
            }
    
            //返回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 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());
            }
    
        }
    
     
    
       
    }

     

    在CppConsoleTest项目的头文件中,添加一个 UserProxy.h 的C++头文件,在文件中添加下面的命名空间:

    using namespace System;
    using namespace System::Reflection;
    using namespace Runtime::InteropServices;
    using namespace System::Collections;

    这样我们就可以使用反射相关的类型了。
    在UserProxy类中,先编写我们需要的构造函数:

    public ref class UserProxy
        {
        private:
            String^ assemblyFile; //"..\NetLib\bin\Debug\NetLib.dll"
            Object^ dotnetObject;
            Type^ entityBuilderType;
            String^  className = "NetLib.User";
    
            EntityHelper^ helper;
    
            
    
        public:
            UserProxy(String^ assemblyFile)
            {
                this->assemblyFile = assemblyFile;
                Assembly^ ass = Assembly::LoadFrom(this->assemblyFile);
                this->dotnetObject = ass->CreateInstance(className);
    
                String^ sodPath = System::IO::Path::Combine(System::IO::Path::GetDirectoryName(this->assemblyFile), "PWMIS.Core.dll");
                /*Assembly^ ass_sod = Assembly::LoadFrom(sodPath);
                this->entityBuilderType = ass_sod->GetType("PWMIS.DataMap.Entity.EntityBuilder");*/
                helper = gcnew EntityHelper(sodPath);
            }
    
    }

    注意我们的 C++/CLI的类必须是“引用”类型,所以需要加关键字 ref,即:

    public ref class UserProxy{}

    所有的.NET引用类型,在使用的时候,都必须在类型名字后加 ^ 符号,例如下面定一个.NET字符串类型变量:

    String^ assemblyFile; 

    带^符号的变量,在C++/CLI中称为 “句柄”对象,用来跟C++本地代码的“指针”相区别。

    在C++中,类的成员用 -> 符号调用,命名空间或者类的静态成员,用::调用,例如上面的构造函数中的代码:

    Assembly^ ass = Assembly::LoadFrom(this->assemblyFile);

     注意:在本例中需要.NET类库项目引用 PDF.NET SOD框架,在项目的“管理Nuget程序包”里面搜索 PDF.NET.SOD.Core 添加此引用即可。
    学会了这些C++的基础语法,那么编写C++/CLI代码就没有主要的障碍了。

    在C++/CLI中使用反射

    反射调用第一个.NET类的方法

    下面的方法,将会反射调用 User类的一个最简单的方法 :

    public int GetUserID(string IdString){}

    该方法只有一个一个参数和一个简单的返回值,下面是C++/CLI的反射调用代码:

    int GetUserID(String^ iDstring)
    {
        MethodInfo^ method = this->dotnetObject->GetType()->GetMethod("GetUserID", BindingFlags::Public | BindingFlags::Instance);
        Func<String^, int>^ fun = (Func<String^, int>^)Delegate::CreateDelegate(Func<String^, int>::typeid, this->dotnetObject, method);
        int result = fun(iDstring);
        
        return result;
    }

    注意这里创建了一个 Func<String,int>的委托方法,使用委托能够简化我们的反射调用并且有时候还能够提高效率,在这段代码中,有1个要注意的地方:
    Func<String^, int>::typeid
    这是C++/CLI特殊的语法,表示获取“句柄”类型的类型ID,实际上它的结果就Type对象,等同于C#的
    typeof(Func<string,int>)

    PS:非常遗憾的是,typeid方式,没法得到下面类型的类型值:
    typeof(Func<,>),这给我们在动态构造泛型对象的时候造成了很大的困惑。

    再看一个简单方法的反射:

    DateTime GetUserBirthday(int userId)
            {
                MethodInfo^ method = dotnetObject->GetType()->GetMethod("GetUserBirthday", BindingFlags::Public | BindingFlags::Instance);
                Func<int, DateTime>^ fun = (Func<int, DateTime>^)Delegate::CreateDelegate(Func<int, DateTime>::typeid, this->dotnetObject, method);
                DateTime result = fun(userId);
                return result;
            }

    注意:由于DateTime是值类型,因此在进行类型申明的时候,不需要加^符号,仅需要对Func委托加上^句柄标记。

    有了这2个简单的方法,我们来看看如何调用这个.NET方法“代理类”:

        NetLibProxy::UserProxy^ proxy = gcnew NetLibProxy::UserProxy("..\NetLib\bin\Debug\NetLib.dll");
        int result= proxy->GetUserID("123456");
        DateTime date = proxy->GetUserBirthday(result);
        System::Console::WriteLine("C++/CLI .Net Proxy Class Call Test Result:
     UserID={0},
     Birthday={1}", 
            result,date.ToShortDateString());

    OK,第一个C++/CLI代码调用成功,而且还是反射调用的,心情小激动一下。

    有关C++/CLI的反射,委托的详细资料,可以参考MSDN的介绍:
    https://msdn.microsoft.com/zh-cn/library/2x8kf7zx.aspx 使用 C++ 互操作(隐式 PInvoke)
    https://msdn.microsoft.com/zh-CN/library/213x8e7w.aspx 泛型委托

    在下一篇,我们将继续探究C++/CLI 反射调用.NET中可能遇到"深坑",因此仅打算吧本篇文章作为一个“入门”,免得大家心生恐惧,错过了挑战艰险的机会。

    (未完待续)

  • 相关阅读:
    Hadoop实战课程
    R语言实战读书笔记(七)基本统计分析
    R语言实战读书笔记(六)基本图形
    python-unexpected content storage modification出错
    python——no module named XX
    R语言实战读书笔记(五)高级数据管理
    R语言缺失值信息处理
    R语言,NA,NAN
    R语言实战读书笔记(四)基本数据管理
    R语言实战读书笔记(三)图形初阶
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/6362908.html
Copyright © 2020-2023  润新知