欢迎光临散文网 会员登陆 & 注册

P/Invoke之C#调用动态链接库DLL

2023-03-29 11:23 作者:百宝门  | 我要投稿


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

  2. 1Visual Studio 2022

  3. 2、.net 6.0

P/Invok是什么?

  1. P/Invoke全称为Platform Invoke(平台调用),

  2. 其实际上就是一种函数调用机制,通过P/Invoke就可以实现调用非托管Dll中的函数。

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

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

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

废话不多说,直接实操

第一步:

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


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


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

第二步:

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



    1. #pragma once

    2. // 定义一些宏

    3. #ifdef __cplusplus

    4. #define EXTERN extern "C"

    5. #else

    6. #define EXTERN

    7. #endif

    8. #define CallingConvention _cdecl

    9. // 判断用户是否有输入,从而定义区分使用dllimport还是dllexport

    10. #ifdef DLL_IMPORT

    11. #define HEAD EXTERN __declspec(dllimport)

    12. #else

    13. #define  HEAD EXTERN __declspec(dllexport)

    14. #endif

    15. HEAD int CallingConvention Sum(int a, int b);

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



    1. #include "Native.h" // 导入头部文件

    2. #include "stdio.h"

    3. HEAD int Add(int a, int b)

    4. {

    5.    return a+b;

    6. }

  3. 在这些步骤做完后,可以尝试生成解决方案,检查是否报错,没有报错之后,将进入项目文件中,检查是否生成DLL (../x64/Debug)



第三步:

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


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


    1. using System.Runtime.InteropServices;

    2. class Program

    3. {

    4.   [DllImport

    5. (@"C:\My_project\C#_Call_C\CSharp_P_Invoke_Dll\x64\Debug\NativeDll.dll")]

    6.   public static extern int Add(int a, int b);

    7.   public static void Main(string[] args)

    8.   {

    9.       int sum = Add(23, 45);

    10.       Console.WriteLine(sum);

    11.       Console.ReadKey();

    12.   }

    13. }

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

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

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



    1. public static void SetDllImportResolver

    2. (System.Reflection.Assembly assembly,

    3. System.Runtime.InteropServices.DllImportResolver resolver);

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


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


    1. public delegate IntPtr DllImportResolver

    2. (string libraryName, Assembly assembly, DllImportSearchPath? searchPath);

  3. 实现resolver方法:


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


    1. const string NativeLib = "NativeDll.dll";

    2. static IntPtr DllImportResolver

    3. (string libraryName, Assembly assembly, DllImportSearchPath? searchPath)

    4. {

    5.    string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory)

    6.   .Parent.Parent.Parent.Parent.ToString(), "x64","Release", "NativeDll.dll");

    7.   // 此处为Dll的路径

    8.    //Console.WriteLine(dll);

    9.    return libraryName switch

    10.    {

    11.        NativeLib => NativeLibrary.Load(dll, assembly, searchPath),

    12.        _ => IntPtr.Zero

    13.    };

    14. }

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



    1. using System.Reflection;

    2. using System.Runtime.InteropServices;

    3. class Program

    4. {

    5.    const string NativeLib = "NativeDll.dll";

    6.    [DllImport(NativeLib)]

    7.    public static extern int Add(int a, int b);

    8.    static IntPtr DllImportResolver

    9.   (string libraryName, Assembly assembly, DllImportSearchPath? searchPath)

    10.    {

    11.        string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory)

    12.        .Parent.Parent.Parent.Parent.ToString(), "x64","Release", "NativeDll.dll");

    13.        Console.WriteLine(dll);

    14.        return libraryName switch

    15.        {

    16.            NativeLib => NativeLibrary.Load(dll, assembly, searchPath),

    17.            _ => IntPtr.Zero

    18.        };

    19.    }

    20.    public static void Main(string[] args)

    21.    {

    22.        NativeLibrary.SetDllImportResolver

    23.       (Assembly.GetExecutingAssembly(), DllImportResolver);

    24.        int sum = Add(23, 45);

    25.        Console.WriteLine(sum);

    26.        Console.ReadKey();

    27.    }

    28. }

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

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

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

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



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

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



    1. HEAD void CBubbleSort(int* array, int length)

    2. {

    3.    int temp = 0;

    4.    for (int i = 0; i < length; i++)

    5.    {

    6.        for (int j = i + 1; j < length; j++)

    7.        {

    8.            if (array[i] > array[j])

    9.            {

    10.                temp = array[i];

    11.                array[i] = array[j];

    12.                array[j] = temp;

    13.            }

    14.        }

    15.    }

    16. }

  3. C#中的代码修改:



    1. using System.Diagnostics;

    2. using System.Reflection;

    3. using System.Runtime.InteropServices;

    4. class Program

    5. {

    6.    const string NativeLib = "NativeDll.dll";

    7.    [DllImport(NativeLib)]

    8.    public unsafe static extern void CBubbleSort(int* arr, int length);

    9.    static IntPtr DllImportResolver

    10.   (string libraryName, Assembly assembly, DllImportSearchPath? searchPath)

    11.    {

    12.        string dll = Path.Combine(new DirectoryInfo(Environment.CurrentDirectory)

    13.        .Parent.Parent.Parent.Parent.ToString(), "x64", "Release", "NativeDll.dll");

    14.        //Console.WriteLine(dll);

    15.        return libraryName switch

    16.        {

    17.            NativeLib => NativeLibrary.Load(dll, assembly, searchPath),

    18.            _ => IntPtr.Zero

    19.        };

    20.    }

    21.    public unsafe static void Main(string[] args)

    22.    {

    23.        int num = 1000;

    24.        int[] arr = new int[num];

    25.        int[] cSharpResult = new int[num];

    26.        //随机生成num数量个(0-10000)的数字

    27.        Random random = new Random();

    28.        for (int i = 0; i < arr.Length; i++)

    29.        {

    30.            arr[i] = random.Next(10000);

    31.        }

    32.        //利用冒泡排序对其数组进行排序

    33.        Stopwatch sw = Stopwatch.StartNew();

    34.        Array.Copy(arr, cSharpResult, arr.Length);

    35.        cSharpResult = BubbleSort(cSharpResult);

    36.        Console.WriteLine($"\n C#实现排序所耗时:{sw.ElapsedMilliseconds}ms\n");

    37.        // 调用Dll中的冒泡排序算法

    38.        NativeLibrary.SetDllImportResolver

    39.       (Assembly.GetExecutingAssembly(), DllImportResolver);

    40.        fixed (int* ptr = &arr[0])

    41.        {

    42.            sw.Restart();

    43.            CBubbleSort(ptr, arr.Length);

    44.        }

    45.        Console.WriteLine($"\n C实现排序所耗时:{sw.ElapsedMilliseconds}ms");

    46.        Console.ReadKey();

    47.    }

    48.    //冒泡排序算法

    49.    public static int[] BubbleSort(int[] array)

    50.    {

    51.        int temp = 0;

    52.        for (int i = 0; i < array.Length; i++)

    53.        {

    54.            for (int j = i + 1; j < array.Length; j++)

    55.            {

    56.                if (array[i] > array[j])

    57.                {

    58.                    temp = array[i];

    59.                    array[i] = array[j];

    60.                    array[j] = temp;

    61.                }

    62.            }

    63.        }

    64.        return array;

    65.    }

    66. }

  4. 执行结果:


    在实现本案例中,可能在编译后,大家所看到的结果不是很出乎意料,但这只是一种案例,希望通过此案例的分析,能给大家带来一些意想不到的收获叭。


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

    2. C实现排序所耗时:3ms

最后

简单做一下总结叭,通过上述所描述的从第一步如何创建一个DLL到如何通过C#去调用的一个简单实例,也应该能给正在查阅相关资料的你有所收获,也希望能给在这方面有所研究的你有一些相关的启发,同时也希望能给目前对这方面毫无了解的你有一个更进一步的学习。


原文地址:P/Invoke之C#调用动态链接库DLL - 百宝门的博客 (baibaomen.com)


P/Invoke之C#调用动态链接库DLL的评论 (共 条)

分享到微博请遵守国家法律