在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 UserDb = new List();

  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();

  userinfo.ID = userId;

  userinfo.Name = "姓名_" + userId;

  userinfo.Birthday = new DateTime(1980, 1, 1);

  return userinfo;

  }

  //返回List或者数组,不影响 C++调用

  public List GetUsers(string likeName)

  {

  List users = new List();

  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 users)

  {

  UserDb.AddRange(users);

  return true;

  }

  public IUserInfo CreateUserObject()

  {

  return EntityBuilder.CreateEntity();

  }

  public bool SaveUsers2(IEnumerable 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^ fun = (Func^)Delegate::CreateDelegate(Func::typeid, this->dotnetObject, method);

  int result = fun(iDstring);

  return result;

  }

  注意这里创建了一个 Func的委托方法,使用委托能够简化我们的反射调用并且有时候还能够提高效率,在这段代码中,有1个要注意的地方:

  Func::typeid

  这是C++/CLI特殊的语法,表示获取“句柄”类型的类型ID,实际上它的结果就Type对象,等同于C#的

  PS:非常遗憾的是,typeid方式,没法得到下面类型的类型值:

  typeof(Func<,>),这给我们在动态构造泛型对象的时候造成了很大的困惑。

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

  DateTime GetUserBirthday(int userId)

  {

  MethodInfo^ method = dotnetObject->GetType()->GetMethod("GetUserBirthday", BindingFlags::Public | BindingFlags::Instance);

  Func^ fun = (Func^)Delegate::CreateDelegate(Func::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中可能遇到"深坑",因此仅打算吧本篇文章作为一个“入门”,免得大家心生恐惧,错过了挑战艰险的机会。

  以上所述是小编给大家介绍的在C++中反射调用.NET的方法(一),希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

  您可能感兴趣的文章:

您可能感兴趣的文章:

相关文章