C#动态调用C++接口

  • Post author:
  • Post category:其他


有些场景需要用到C#调用C++编译的接口,这就要使用[DllImport]直接调用,该[DllImport]所在的名字空间为using System.Runtime.InteropServices;当我们得到一个C++的DLL接口时,我们可以使用[DllImport]调用该DLL就得知道DLL的接口,传入的参数、传出的参数、入口函数。(包括DLL的路径)

如:

[DllImport(@"\..\..\Platform\DLL\TEST.dll", EntryPoint = "uds_calc_key", CharSet = CharSet.Ansi)]
private static extern void uds_calc_key(string seed, StringBuilder key);

上面定义了私有的静态方法包含两个参数,可以看到TEST.dll接口的入口点是uds_calc_key,该方法第一个传入参数类型是string类型,第二个是StringBuilder类型。

但是还要在下面再定义了一个方法去调用上面的DLL的入口函数也就是私有的静态方法

 public ActionResult returnKey(string seed)
 {
     StringBuilder stringBuilder = new StringBuilder();
     uds_calc_key(seed, stringBuilder);
     string strBuilder = stringBuilder.ToString();
     return Json(strBuilder, JsonRequestBehavior.AllowGet);
 }

现在知道了怎么去调用C++编译的DLL了,如果有一个需求可以动态更新DLL,那又怎么去调用DLL呢?这就要利用C#的反射的知识了。也就是Assembly反射,所谓 Assembly定义和加载程序集,就是加载在程序集清单中列出模块,以及从程序集中查找类型并创建该类型的。在控制台输出做了一个demo实例。这里需要建立两个项目。

第一个项目:生成反射后的dll接口,里面传递任意参数都可以。

static void Main(string[] args)
 {
     CompilerResults resualt =  DebugRun(@"D:\"); //生成dll的路径 目标mydll.dll
     if (resualt.Errors.HasErrors)
     {
         Console.WriteLine("编译错误:");
         foreach (CompilerError err in resualt.Errors)
         {
             Console.WriteLine(err.ErrorText);
         }
     }
     Console.ReadLine();
 }
  public static CompilerResults DebugRun(string newPath)
  {
      ICodeCompiler complier = new CSharpCodeProvider().CreateCompiler();
      //设置编译参数
      CompilerParameters paras = new CompilerParameters();
      //引入第三方dll
      paras.ReferencedAssemblies.Add("System.dll");
      //引入自定义dll
      //paras.ReferencedAssemblies.Add(@"D:\自定义方法\自定义方法\bin\LogHelper.dll");
      //是否内存中生成输出 
      paras.GenerateInMemory = false;
      //是否生成可执行文件
      paras.GenerateExecutable = false;
      paras.OutputAssembly = newPath + "mydll.dll";

      //编译代码
      CompilerResults result = complier.CompileAssemblyFromSource(paras, GenerateCode());
          return result;
  }

  static string GenerateCode()
  {
      //这里写mydll.cs 的内容   就是一个cs 模板    部分内容需要根据变量替换

      StringBuilder sb = new StringBuilder();
      sb.Append("using System;");
      sb.Append(Environment.NewLine);
      sb.Append("using System.Text;");
      sb.Append(Environment.NewLine);
      sb.Append("using System.Runtime.InteropServices;");
      sb.Append(Environment.NewLine);
      sb.Append("namespace mydll");    //可以用变量替换
      sb.Append(Environment.NewLine);
      sb.Append("{");
      sb.Append(Environment.NewLine);
      sb.Append("    public class Class1");//可以用变量替换
      sb.Append(Environment.NewLine);
      sb.Append("    {");
      sb.Append(Environment.NewLine);可以用变量替换
      sb.Append("        [DllImport(@\"E:\\SGMWVin2PinCN200S.dll\", EntryPoint = \"VinToPin\", CharSet = CharSet.Ansi)]");
      sb.Append(Environment.NewLine);
      sb.Append("        private static extern void VinToPin(string Vin, StringBuilder Pin);"); //可以用变量替换
      sb.Append(Environment.NewLine);
      sb.Append("        public static string returnKey(string seed)");//可以用变量替换
      sb.Append(Environment.NewLine);
      sb.Append("        {");
      sb.Append(Environment.NewLine);
      sb.Append("        StringBuilder stringBuilder = new StringBuilder();");
      sb.Append(Environment.NewLine);
      sb.Append("        VinToPin(seed, stringBuilder);");
      sb.Append(Environment.NewLine);
      sb.Append("        string strBuilder = stringBuilder.ToString();");
      sb.Append(Environment.NewLine);
      sb.Append("        return strBuilder;");
      sb.Append(Environment.NewLine);
      sb.Append("        }");
      sb.Append(Environment.NewLine);
      sb.Append("   }");
      sb.Append(Environment.NewLine);
      sb.Append("}");
      string code = sb.ToString();
      //Console.WriteLine(code);
     //Console.WriteLine();
      return code;
  }

第二个项目:我们就会想既然已经动态生成反射dll了,那么怎么去调用反射后dll接口呢。

在Main写以下代码就行了

static void Main(string[] args)
 {
      //string path = @"D:\SGMWVin2ESK_CN200S.dll";

      string path = @"D:\mydll.dll";   //调用生成的dll
      Assembly assem = Assembly.LoadFile(path);

      Type[] tys = assem.GetTypes();//只好得到所有的类型名,然后遍历,通过类型名字来区别了
      foreach (Type ty in tys)//huoquleiming
      {
          //Console.WriteLine("class name is " + ty.Name);
          MethodInfo[] listMethodInfo = ty.GetMethods();
          foreach (MethodInfo methodInfo in listMethodInfo)
          {

              if (methodInfo.Name == "returnKey") {//要调用的mydll的函数
                  object magicClassObject = Activator.CreateInstance(ty);
                  MethodInfo mi = ty.GetMethod("returnKey");  //要调用的mydll的函数
                  object[] obj = new object[1];
                  obj[0] = "123456789";  //要传入的参数 
                  object aa = mi.Invoke(magicClassObject, obj);   //返回值
                  Console.WriteLine(aa.ToString());  //打印
              }
              Console.WriteLine("Method name is " + methodInfo.Name);
          }
              
          //ConstructorInfo magicConstructor = ty.GetConstructor(Type.EmptyTypes);//获取不带参数的构造函数
          //object magicClassObject = magicConstructor.Invoke(new object[] { });//这里是获取一个类似于类的实例的东东

          //object magicClassObject = Activator.CreateInstance(ty);//获取无参数的构造实例还可以通过这样
              
              //MethodInfo mi = ty.GetMethod("sayhello");
              //object aa = mi.Invoke(magicClassObject, null);
             // Console.WriteLine(aa.ToString());//这儿是执行类class1的sayhello方法
         
      }
      Console.ReadLine();
  }

现在两个项目成功完成,那么就可以C#动态调用C++接口了。这里面好多需要普及的知识点,为了更加深入的了解,就不得不需要花费更多的时间和精力。这里有个缺点就是每当反射一个DLL文件时,文件夹就多了一个DLL文件,或者生成不了,直接报错因为已经生成了DLL文件了,当我们肯定会想如果不需要时,就要删除之前的DLL文件,由于反射后的DLL仍处于锁定中,使用LoadFile读取DLL文件是释放不了的,要每次反射完DLL去释放的话,就要用到Load去读取DLL文件。



版权声明:本文为weixin_44548643原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。