在C++中反射调用.NET(一) 反射调用第一个.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:\r\n UserID={0},\r\n 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中可能遇到"深坑",因此仅打算吧本篇文章作为一个“入门”,免得大家心生恐惧,错过了挑战艰险的机会。

(未完待续)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏草根专栏

设计模式学习(四): 1.简单工厂 (附C#实现)

New ? ? 这是一个典型的情况, 我们需要在运行时来实例化一些具体的类. 在需要修改或者扩展的时候我们就需要改这段代码. 一个程序中可能会多次出现类似的代码...

32260
来自专栏领域驱动设计DDD实战进阶

05-TypeScript中的方法新功能(下)

再TypeScript中,方法还有一些新功能能够让我们更好的控制方法执行。 1.Generator方法: yield关键字用于控制方法在执行的时候暂停住,后续方...

28450
来自专栏Python小屋

Python版归并排序算法(附Python程序__name__属性用法演示视频)

import random def mergeSort(seq, reverse=False): #把原列表分成两部分 mid = len(s...

36460
来自专栏腾讯IVWEB团队的专栏

踩坑记:当 JavaScript 遇上 UINT 64

写下这篇文章的缘由是因为在项目过程中,碰到了一个使用 JavaScript 处理 UINT64 类型数字的坑。二进制浮点数中的 0.1 和 0.2 并不是十分...

82900
来自专栏小樱的经验随笔

Codeforces 714A Meeting of Old Friends

A. Meeting of Old Friends time limit per test:1 second memory limit per test:256...

395100
来自专栏拭心的安卓进阶之路

变种 Builder 模式:优雅的对象构建方式

帅气的 Builder 链式调用 在日常开发中,经常可以看到这样的代码: Retrofit retrofit = new Retrofit.Builder() ...

27890
来自专栏大内老A

WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

大部分的系统都是以数据为中心的(Data Central),功能的实现表现在对相关数据的正确处理。而数据本身,是有效信息的载体,在不同的环境具有不同的表示。一个...

36680
来自专栏林德熙的博客

C# 序列类为 xml 可以使用的特性大全

本文告诉大家如何使用序列类,以及序列时可以用到的特性,特性的作用和一些容易被问的问题

15110
来自专栏大内老A

EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG

在《EnterLib PIAB又一个BUG?》这篇文章中我们谈到:当我们通过应用DependencyAttribute特性定义需要自动注入的属性的时候,当这个属...

24890
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[泛型集合、选择集合、集合的安全]

    软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类。不管是数组还是集合类,它们都有各自的优缺点。如何使用好集合是我们在开发过程中必须...

8920

扫码关注云+社区

领取腾讯云代金券