C#动态链接库(dll)调用全攻略

本编所涉及到的工具以及框架:
1、Visual Studio 2022
2、.net 7.0

P/Invok是什么?

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

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

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

废话不多说,直接实操

第一步:

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

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

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

第二步:

  1. 在我们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);
    
  2. 之后需要去实现头文件中的方法,在Native.c中实现,具体实现如下:

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

第三步:

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

    using System.Runtime.InteropServices;
    
    class Program
    {
        [DllImport(@"C:\My_project\C#_Call_C\CSharp_P_Invoke_Dll\x64\Debug\NativeDll.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#中的代码块做一些改动,便于维护

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

    public static void SetDllImportResolver (System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportResolver resolver);
    
  2. 在使用这个方法前,先查看一下其参数

    a、assembly: 主要是获取包含当前正在执行的代码的程序集(不过多讲解)

    b、resolber: 此参数是我们要注重实现的,我们可以通过查看他的元代码,发现其实现的是一个委托,因此我们对其进行实现。

    原始方法如下:

    public delegate IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath);
    
  3. 实现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的路径时,只需要在这个方法中对其相对路径进行修改即可。

  4. 更新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();
        }
    }
    
  5. 最后重新编译,检查其是否能顺利编译通过,最终我们的到的结果为:68

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

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

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

    HEAD void CBubbleSort(int* array, int length);
    
  2. 在.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;
                }
            }
        }
    }
    
  3. 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 = 10000;
            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}ms\n");
    
            // 调用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;
        }
    }
    
  4. 执行结果:

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

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

最后

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

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt是一个跨平台的应用程序开发框架,可以用于开发图形用户界面(GUI)和命令行界面应用程序。Qt支持C++编程语言,并提供了许多对C++的扩展和封装,使得开发者可以更方便地使用C++语言来开发应用程序。 要使用Qt调用C语言代码,我们可以使用Qt提供的外部函数接口。外部函数接口允许我们在Qt应用程序中调用C语言代码,并将数据传递给C函数进行处理,然后将处理后的结果返回给Qt应用程序。 为了调用C语言代码,我们需要做以下几个步骤: 1. 在Qt应用程序中包含C语言代码需要的头文件。这些头文件通常包含了C函数的声明和定义。 2. 使用Qt提供的外部函数接口将C函数导入到Qt应用程序中。这可以通过使用"extern"关键字将C函数声明为外部函数来实现。 3. 在Qt应用程序中调用C函数。我们可以通过调用C函数的名称和提供正确的参数来调用C函数。 4. 处理C函数的返回值。当C函数完成任务并返回结果时,我们可以获取返回值,并根据需要在Qt应用程序中进行进一步处理。 需要注意的是,在调用C语言代码时,我们需要确保应用程序和C代码之间的数据类型和参数匹配。Qt提供了一些工具和类来处理不同数据类型之间的转换,以便更方便地进行数据的传递和处理。 总之,Qt可以方便地与C语言代码进行交互,通过外部函数接口和正确的数据类型转换,我们可以在Qt应用程序中调用和使用C函数。这使得我们可以利用C语言的特性和函数库来扩展和增强Qt应用程序的功能。 ### 回答2: Qt是一个跨平台的应用程序开发框架,它提供了丰富的类库和工具来简化和加速应用程序的开发过程。Qt使用C++语言编写,但是也支持其他编程语言的接口,如Python、Java等。 要在Qt中调用C语言库函数,可以通过以下步骤实现。 首先,将C语言库函数的头文件包含在Qt项目中。在需要调用C语言函数的源文件中,使用#include指令将C语言库的头文件包含进来,以便能够访问C语言库的函数和数据结构。 其次,在Qt项目的.pro文件中添加对应的C语言库依赖。在CONFIG变量中添加LIBS关键字,并用方括号括起来添加C语言库的名称。比如,如果要调用名为libexample.so的C语言库,可以在.pro文件中添加LIBS += [-lexample]。 然后,在Qt的源文件中编写调用C语言库函数的代码。根据C语言库的接口和功能要求,使用C++代码调用C语言库提供的函数,并适当处理返回值或错误。 最后,编译和运行Qt项目。在构建过程中,Qt会链接C语言库,并生成可执行文件。运行可执行文件时,C语言库的函数能够被正确调用和执行。 总结来说,Qt调用C语言库函数的关键在于包含头文件、添加库依赖、编写调用代码和正确链接。通过这些步骤,能够在Qt项目中成功调用C语言库函数,实现更丰富的功能。 ### 回答3: Qt是一个跨平台的C++应用程序开发框架,提供了丰富的类库和工具,简化了C++程序的开发过程。Qt调用C是指在Qt应用程序中调用C语言的函数或库。 Qt提供了方便的机制来实现Qt与C代码之间的交互。首先,可以使用Qt的信号和槽机制来连接Qt对象和C函数。通过定义一个QMetaObject类型的对象,可以将C函数转换为Qt的信号和槽,从而实现二者之间的通信。 还可以使用Qt的QProcess类来启动一个外部的C程序,并与之进行通信。通过QProcess的相关方法,可以实现C程序的调用和数据的传递。 此外,Qt还提供了对C函数和库的直接调用的支持。可以使用Qt的QLibrary类动态加载C函数库,并通过获取函数指针来调用C函数。QApplication类中的exec()函数的实现就是一个例子,它通过直接调用C函数进入Qt应用程序的事件循环。 在Qt中调用C函数或库时,需要注意一些细节。首先,需要确保编译器能够正确地识别C函数的声明。一般来说,可以使用extern "C"关键字将C函数声明为C语言形式,避免C++的函数名重整机制。其次,需要注意数据类型的转换问题,例如将Qt中的QString转换为C中的char数组。可以使用QString的toUtf8()函数将QString转换为UTF-8编码的字符数组。 总而言之,Qt提供了多种方式来实现Qt与C之间的调用,开发者可以根据具体的需求选择合适的方法。这些方法能够方便地实现Qt应用程序与C函数或库的交互,提高程序的灵活性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值