P/Invoke之C#調(diào)用動態(tài)鏈接庫DLL示例詳解
P/Invok是什么?
本編所涉及到的工具以及框架:
1、Visual Studio 2022
2、.net 6.0
P/Invoke全稱為Platform Invoke(平臺調(diào)用),其實際上就是一種函數(shù)調(diào)用機(jī)制,通過P/Invoke就可以實現(xiàn)調(diào)用非托管Dll中的函數(shù)。
在開始之前,我們首先需要了解C#中有關(guān)托管與非托管的區(qū)別
托管(Collocation),即在程序運行時會自動釋放內(nèi)存;
非托管,即在程序運行時不會自動釋放內(nèi)存。
廢話不多說,直接實操
第一步:
- 打開VS2022,新建一個C#控制臺應(yīng)用
- 右擊解決方案,添加一個新建項,新建一個"動態(tài)鏈接庫(DLL)",新建完之后需要右擊當(dāng)前項目--> 屬性 --> C/C++ --> 預(yù)編譯頭 --> 選擇"不使用編譯頭"
在新建的DLL中我們新建一個頭文件,用于編寫我們的方法定義,然后再次新建一個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);
之后需要去實現(xiàn)頭文件中的方法,在Native.c中實現(xiàn),具體實現(xiàn)如下:
#include "Native.h" // 導(dǎo)入頭部文件
#include "stdio.h"
HEAD int Add(int a, int b)
{
return a+b;
}
- 在這些步驟做完后,可以嘗試生成解決方案,檢查是否報錯,沒有報錯之后,將進(jìn)入項目文件中,檢查是否生成DLL (../x64/Debug)
第三步:
在這里之后,就可以在C#中去嘗試調(diào)用剛剛所聲明的方法,以便驗證是否調(diào)用DLL成功,其具體實現(xiàn)如下:
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();
}
}
運行結(jié)果為:68,證明我們成功調(diào)用了DLL動態(tài)鏈庫
C#中通過P/Invoke調(diào)用DLL動態(tài)鏈庫的流程
通過上述一個簡單的例子,我們大致了解到了在C#中通過P/Invoke調(diào)用DLL動態(tài)鏈庫的流程,接下我們將對C#中的代碼塊做一些改動,便于維護(hù)
在改動中我們將用到NativeLibrary類中的一個方法,用于設(shè)置回調(diào),解析從程序集進(jìn)行的本機(jī)庫導(dǎo)入,并實現(xiàn)通過設(shè)置DLL的相對路徑進(jìn)行加載,其方法如下:
public static void SetDllImportResolver (System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportResolver resolver);
在使用這個方法前,先查看一下其參數(shù)
a、assembly: 主要是獲取包含當(dāng)前正在執(zhí)行的代碼的程序集(不過多講解)
b、resolber: 此參數(shù)是我們要注重實現(xiàn)的,我們可以通過查看他的元代碼,發(fā)現(xiàn)其實現(xiàn)的是一個委托,因此我們對其進(jìn)行實現(xiàn)。
原始方法如下:
public delegate IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath);
實現(xiàn)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
};
}
該方法主要是用于區(qū)分在加載DLL時不一定只能是設(shè)置絕對路徑,也可以使用相對路徑對其加載,本區(qū)域代碼是通過使用委托去實現(xiàn)加載相對路徑對其DLL加載,這樣做的好處是,便于以后需要更改DLL的路徑時,只需要在這個方法中對其相對路徑進(jìn)行修改即可。
更新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();
}
}
最后重新編譯,檢查其是否能順利編譯通過,最終我們的到的結(jié)果為:68
至此,我們就完成了一個簡單的C#調(diào)用動態(tài)鏈接庫的案例
下面將通過一個具體實例,講述為什么要這樣做?(本實例通過從性能方面進(jìn)行對比)
在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ù)量個(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#實現(xiàn)排序所耗時:{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實現(xiàn)排序所耗時:{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é)果:
C#實現(xiàn)排序所耗時: 130ms
C實現(xiàn)排序所耗時:3ms
在實現(xiàn)本案例中,可能在編譯后,大家所看到的結(jié)果不是很出乎意料,但這只是一種案例,希望通過此案例的分析,能給大家?guī)硪恍┮庀氩坏降氖斋@叭。
最后
簡單做一下總結(jié)叭,通過上述所描述的從第一步如何創(chuàng)建一個DLL到如何通過C#去調(diào)用的一個簡單實例,也應(yīng)該能給正在查閱相關(guān)資料的你有所收獲,也希望能給在這方面有所研究的你有一些相關(guān)的啟發(fā),同時也希望能給目前對這方面毫無了解的你有一個更進(jìn)一步的學(xué)習(xí)。
以上就是P/Invoke之C#調(diào)用動態(tài)鏈接庫DLL示例詳解的詳細(xì)內(nèi)容,更多關(guān)于P/Invoke C#調(diào)用DLL的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Datagridview使用技巧(9)Datagridview的右鍵菜單
這篇文章主要為大家詳細(xì)介紹了Datagridview使用技巧,Datagridview的右鍵菜單,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
C# Socket通信的實現(xiàn)(同時監(jiān)聽多客戶端)
這篇文章主要介紹了C# Socket通信的實現(xiàn)(同時監(jiān)聽多客戶端),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
C#并發(fā)容器之ConcurrentDictionary與普通Dictionary帶鎖性能詳解
這篇文章主要介紹了C#并發(fā)容器之ConcurrentDictionary與普通Dictionary帶鎖性能詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
C# WINFORM 強(qiáng)制讓窗體獲得焦點的方法代碼
C# WINFORM 強(qiáng)制讓窗體獲得焦點的方法代碼,需要的朋友可以參考一下2013-04-04

