验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

P/Invoke之C#调用动态链接库DLL的方法是什么

阅读:1092 来源:乙速云 作者:代码code

P/Invoke之C#调用动态链接库DLL的方法是什么

      P/Invok是什么?

      本编所涉及到的工具以及框架:

      1、Visual Studio 2022

      2、.net 6.0

      P/Invoke全称为Platform Invoke(平台调用),其实际上就是一种函数调用机制,通过P/Invoke就可以实现调用非托管Dll中的函数。

      在开始之前,我们首先需要了解C#中有关托管与非托管的区别

      托管(Collocation),即在程序运行时会自动释放内存;

      非托管,即在程序运行时不会自动释放内存。

      废话不多说,直接实操

      第一步:

      • 打开VS2022,新建一个C#控制台应用

      P/Invoke之C#调用动态链接库DLL的方法是什么

      • 右击解决方案,添加一个新建项,新建一个"动态链接库(DLL)",新建完之后需要右击当前项目--> 属性 --> C/C++ --> 预编译头 --> 选择"不使用编译头"

      P/Invoke之C#调用动态链接库DLL的方法是什么

      在新建的DLL中我们新建一个头文件,用于编写我们的方法定义,然后再次新建一个C++文件,后缀以.c 结尾

      P/Invoke之C#调用动态链接库DLL的方法是什么

      第二步:

      在我们DLL中的头文件(Native.h)中定义相关的Test方法,具体代码如下:

      #pragma once
      // 定义一些宏
      #ifdef __cplusplus
      #define EXTERN extern "C"
      #else
      #define EXTERN
      #endif
      #define CallingConvention _cdecl
      // 判断用户是否有输入,从而定义区分使用dllimport还是dllexport
      #ifdef DLL_IMPORT 
      #define HEAD EXTERN __declspec(dllimport)
      #else
      #define  HEAD EXTERN __declspec(dllexport)
      #endif
      HEAD int CallingConvention Sum(int a, int b);

      之后需要去实现头文件中的方法,在Native.c中实现,具体实现如下:

      #include "Native.h" // 导入头部文件
      #include "stdio.h"
      HEAD int Add(int a, int b)
      {
          return a+b;
      }
      • 在这些步骤做完后,可以尝试生成解决方案,检查是否报错,没有报错之后,将进入项目文件中,检查是否生成DLL (../x64/Debug)

      P/Invoke之C#调用动态链接库DLL的方法是什么

      第三步:

      在这里之后,就可以在C#中去尝试调用刚刚所声明的方法,以便验证是否调用DLL成功,其具体实现如下:

      using System.Runtime.InteropServices;
      class Program
      {
          [DllImport(@"C:My_projectC#_Call_CCSharp_P_Invoke_Dllx64DebugNativeDll.dll")]
          public static extern int Add(int a, int b);
          public static void Main(string[] args)
          {
              int sum = Add(23, 45);
              Console.WriteLine(sum);
              Console.ReadKey();
          }
      }

      运行结果为:68,证明我们成功调用了DLL动态链库

      C#中通过P/Invoke调用DLL动态链库的流程

        通过上述一个简单的例子,我们大致了解到了在C#中通过P/Invoke调用DLL动态链库的流程,接下我们将对C#中的代码块做一些改动,便于维护

      在改动中我们将用到NativeLibrary类中的一个方法,用于设置回调,解析从程序集进行的本机库导入,并实现通过设置DLL的相对路径进行加载,其方法如下:

      public static void SetDllImportResolver (System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportResolver resolver);

      在使用这个方法前,先查看一下其参数

      a、assembly: 主要是获取包含当前正在执行的代码的程序集(不过多讲解)
      b、resolber: 此参数是我们要注重实现的,我们可以通过查看他的元代码,发现其实现的是一个委托,因此我们对其进行实现。
      原始方法如下:

      public delegate IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath);

      实现resolver方法:

      const string NativeLib = "NativeDll.dll";
      static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
      {
          string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.Parent.Parent.ToString(), "x64","Release", "NativeDll.dll"); // 此处为Dll的路径
          //Console.WriteLine(dll);
          return libraryName switch
          {
              NativeLib => NativeLibrary.Load(dll, assembly, searchPath),
              _ => IntPtr.Zero
          };
      }

      该方法主要是用于区分在加载DLL时不一定只能是设置绝对路径,也可以使用相对路径对其加载,本区域代码是通过使用委托去实现加载相对路径对其DLL加载,这样做的好处是,便于以后需要更改DLL的路径时,只需要在这个方法中对其相对路径进行修改即可。

      更新C#中的代码,其代码如下:

      using System.Reflection;
      using System.Runtime.InteropServices;
      class Program
      {
          const string NativeLib = "NativeDll.dll";
          [DllImport(NativeLib)]
          public static extern int Add(int a, int b);
          static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
          {
              string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.Parent.Parent.ToString(), "x64","Release", "NativeDll.dll");
              Console.WriteLine(dll);
              return libraryName switch
              {
                  NativeLib => NativeLibrary.Load(dll, assembly, searchPath),
                  _ => IntPtr.Zero
              };
          }
          public static void Main(string[] args)
          {
              NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver);
              int sum = Add(23, 45);
              Console.WriteLine(sum);
              Console.ReadKey();
          }
      }

      最后重新编译,检查其是否能顺利编译通过,最终我们的到的结果为:68

      至此,我们就完成了一个简单的C#调用动态链接库的案例

        下面将通过一个具体实例,讲述为什么要这样做?(本实例通过从性能方面进行对比)

      在DLL中的头文件中,加入如下代码:

      HEAD void CBubbleSort(int* array, int length);

      在.c文件中加入如下代码:

      HEAD void CBubbleSort(int* array, int length)
      {
          int temp = 0;
          for (int i = 0; i < length; i++)
          {
              for (int j = i + 1; j < length; j++)
              {
                  if (array[i] > array[j])
                  {
                      temp = array[i];
                      array[i] = array[j];
                      array[j] = temp;
                  }
              }
          }
      }

      C#中的代码修改:

      using System.Diagnostics;
      using System.Reflection;
      using System.Runtime.InteropServices;
      class Program
      {
          const string NativeLib = "NativeDll.dll";
          [DllImport(NativeLib)]
          public unsafe static extern void CBubbleSort(int* arr, int length);
          static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
          {
              string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.Parent.Parent.ToString(), "x64", "Release", "NativeDll.dll");
              //Console.WriteLine(dll);
              return libraryName switch
              {
                  NativeLib => NativeLibrary.Load(dll, assembly, searchPath),
                  _ => IntPtr.Zero
              };
          }
          public unsafe static void Main(string[] args)
          {
              int num = 1000;
              int[] arr = new int[num];
              int[] cSharpResult = new int[num];
              //随机生成num数量个(0-10000)的数字
              Random random = new Random();
              for (int i = 0; i < arr.Length; i++)
              {
                  arr[i] = random.Next(10000);
              }
              //利用冒泡排序对其数组进行排序
              Stopwatch sw = Stopwatch.StartNew();
              Array.Copy(arr, cSharpResult, arr.Length);
              cSharpResult = BubbleSort(cSharpResult);
              Console.WriteLine($"n C#实现排序所耗时:{sw.ElapsedMilliseconds}msn");
              // 调用Dll中的冒泡排序算法
              NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver);
              fixed (int* ptr = &arr[0])
              {
                  sw.Restart();
                  CBubbleSort(ptr, arr.Length);
              }
              Console.WriteLine($"n C实现排序所耗时:{sw.ElapsedMilliseconds}ms");
              Console.ReadKey();
          }
          //冒泡排序算法
          public static int[] BubbleSort(int[] array)
          {
              int temp = 0;
              for (int i = 0; i < array.Length; i++)
              {
                  for (int j = i + 1; j < array.Length; j++)
                  {
                      if (array[i] > array[j])
                      {
                          temp = array[i];
                          array[i] = array[j];
                          array[j] = temp;
                      }
                  }
              }
              return array;
          }
      }

      执行结果:

      C#实现排序所耗时: 130ms
      C实现排序所耗时:3ms

    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>