P/Invoke之C#調(diào)用動(dòng)態(tài)鏈接庫(kù)DLL
本編所涉及到的工具以及框架:
1、Visual Studio 2022
2、.net 6.0
P/Invok是什么?
P/Invoke全稱為Platform Invoke(平臺(tái)調(diào)用),
其實(shí)際上就是一種函數(shù)調(diào)用機(jī)制,通過(guò)P/Invoke就可以實(shí)現(xiàn)調(diào)用非托管Dll中的函數(shù)。
在開(kāi)始之前,我們首先需要了解C#中有關(guān)托管與非托管的區(qū)別
托管(Collocation),即在程序運(yùn)行時(shí)會(huì)自動(dòng)釋放內(nèi)存;
非托管,即在程序運(yùn)行時(shí)不會(huì)自動(dòng)釋放內(nèi)存。
廢話不多說(shuō),直接實(shí)操
第一步:
打開(kāi)VS2022,新建一個(gè)C#控制臺(tái)應(yīng)用

2.右擊解決方案,添加一個(gè)新建項(xiàng),新建一個(gè)"動(dòng)態(tài)鏈接庫(kù)(DLL)",新建完之后需要右擊當(dāng)前項(xiàng)目--> 屬性 --> C/C++ --> 預(yù)編譯頭 --> 選擇"不使用編譯頭"

3. 在新建的DLL中我們新建一個(gè)頭文件,用于編寫我們的方法定義,然后再次新建一個(gè)C++文件,后綴以.c 結(jié)尾

第二步:
在我們DLL中的頭文件(Native.h)中定義相關(guān)的Test方法,具體代碼如下:
#pragma once
// 定義一些宏
#ifdef __cplusplus
#define EXTERN extern "C"
#else
#define EXTERN
#endif
#define CallingConvention _cdecl
// 判斷用戶是否有輸入,從而定義區(qū)分使用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);
之后需要去實(shí)現(xiàn)頭文件中的方法,在Native.c中實(shí)現(xiàn),具體實(shí)現(xiàn)如下:
#include "Native.h" // 導(dǎo)入頭部文件
#include "stdio.h"
HEAD int Add(int a, int b)
{
? ?return a+b;
}
在這些步驟做完后,可以嘗試生成解決方案,檢查是否報(bào)錯(cuò),沒(méi)有報(bào)錯(cuò)之后,將進(jìn)入項(xiàng)目文件中,檢查是否生成DLL (../x64/Debug)

第三步:
在這里之后,就可以在C#中去嘗試調(diào)用剛剛所聲明的方法,以便驗(yàn)證是否調(diào)用DLL成功,其具體實(shí)現(xiàn)如下:
運(yùn)行結(jié)果為:
68
,證明我們成功調(diào)用了DLL動(dòng)態(tài)鏈庫(kù)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();
? }
}
C#中通過(guò)P/Invoke調(diào)用DLL動(dòng)態(tài)鏈庫(kù)的流程
??通過(guò)上述一個(gè)簡(jiǎn)單的例子,我們大致了解到了在C#中通過(guò)P/Invoke調(diào)用DLL動(dòng)態(tài)鏈庫(kù)的流程,接下我們將對(duì)C#中的代碼塊做一些改動(dòng),便于維護(hù)
在改動(dòng)中我們將用到?
NativeLibrary
類中的一個(gè)方法,用于設(shè)置回調(diào),解析從程序集進(jìn)行的本機(jī)庫(kù)導(dǎo)入,并實(shí)現(xiàn)通過(guò)設(shè)置DLL的相對(duì)路徑進(jìn)行加載,其方法如下:public static void SetDllImportResolver
(System.Reflection.Assembly assembly,
System.Runtime.InteropServices.DllImportResolver resolver);
在使用這個(gè)方法前,先查看一下其參數(shù)
a、assembly: 主要是獲取包含當(dāng)前正在執(zhí)行的代碼的程序集(不過(guò)多講解)
b、resolber: 此參數(shù)是我們要注重實(shí)現(xiàn)的,我們可以通過(guò)查看他的元代碼,發(fā)現(xiàn)其實(shí)現(xiàn)的是一個(gè)委托,因此我們對(duì)其進(jìn)行實(shí)現(xiàn)。
原始方法如下:public delegate IntPtr DllImportResolver
(string libraryName, Assembly assembly, DllImportSearchPath? searchPath);
實(shí)現(xiàn)resolver方法:
該方法主要是用于區(qū)分在加載DLL時(shí)不一定只能是設(shè)置絕對(duì)路徑,也可以使用相對(duì)路徑對(duì)其加載,本區(qū)域代碼是通過(guò)使用委托去實(shí)現(xiàn)加載相對(duì)路徑對(duì)其DLL加載,這樣做的好處是,便于以后需要更改DLL的路徑時(shí),只需要在這個(gè)方法中對(duì)其相對(duì)路徑進(jìn)行修改即可。
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
? ?};
}
更新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();
? ?}
}
最后重新編譯,檢查其是否能順利編譯通過(guò),最終我們的到的結(jié)果為:?
68
至此,我們就完成了一個(gè)簡(jiǎn)單的C#調(diào)用動(dòng)態(tài)鏈接庫(kù)的案例
??下面將通過(guò)一個(gè)具體實(shí)例,講述為什么要這樣做?(本實(shí)例通過(guò)從性能方面進(jìn)行對(duì)比)
在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];
? ? ? ?//隨機(jī)生成num數(shù)量個(gè)(0-10000)的數(shù)字
? ? ? ?Random random = new Random();
? ? ? ?for (int i = 0; i < arr.Length; i++)
? ? ? ?{
? ? ? ? ? ?arr[i] = random.Next(10000);
? ? ? ?}
? ? ? ?//利用冒泡排序?qū)ζ鋽?shù)組進(jìn)行排序
? ? ? ?Stopwatch sw = Stopwatch.StartNew();
? ? ? ?Array.Copy(arr, cSharpResult, arr.Length);
? ? ? ?cSharpResult = BubbleSort(cSharpResult);
? ? ? ?Console.WriteLine($"\n C#實(shí)現(xiàn)排序所耗時(shí):{sw.ElapsedMilliseconds}ms\n");
? ? ? ?// 調(diào)用Dll中的冒泡排序算法
? ? ? ?NativeLibrary.SetDllImportResolver
? ? ? (Assembly.GetExecutingAssembly(), DllImportResolver);
? ? ? ?fixed (int* ptr = &arr[0])
? ? ? ?{
? ? ? ? ? ?sw.Restart();
? ? ? ? ? ?CBubbleSort(ptr, arr.Length);
? ? ? ?}
? ? ? ?Console.WriteLine($"\n C實(shí)現(xiàn)排序所耗時(shí):{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;
? ?}
}
執(zhí)行結(jié)果:
在實(shí)現(xiàn)本案例中,可能在編譯后,大家所看到的結(jié)果不是很出乎意料,但這只是一種案例,希望通過(guò)此案例的分析,能給大家?guī)?lái)一些意想不到的收獲叭。
C#實(shí)現(xiàn)排序所耗時(shí): 130ms
C實(shí)現(xiàn)排序所耗時(shí):3ms
最后
簡(jiǎn)單做一下總結(jié)叭,通過(guò)上述所描述的從第一步如何創(chuàng)建一個(gè)DLL到如何通過(guò)C#去調(diào)用的一個(gè)簡(jiǎn)單實(shí)例,也應(yīng)該能給正在查閱相關(guān)資料的你有所收獲,也希望能給在這方面有所研究的你有一些相關(guān)的啟發(fā),同時(shí)也希望能給目前對(duì)這方面毫無(wú)了解的你有一個(gè)更進(jìn)一步的學(xué)習(xí)。
原文地址:P/Invoke之C#調(diào)用動(dòng)態(tài)鏈接庫(kù)DLL - 百寶門的博客 (baibaomen.com)