P/Invoke之C#调用动态链接库DLL
本编所涉及到的工具以及框架:
1、Visual Studio 2022
2、.net 6.0
P/Invok是什么?
P/Invoke全称为Platform Invoke(平台调用),其实际上就是一种函数调用机制,通过P/Invoke就可以实现调用非托管Dll中的函数。
在开始之前,我们首先需要了解C#中有关托管与非托管的区别
托管(Collocation),即在程序运行时会自动释放内存;
非托管,即在程序运行时不会自动释放内存。
废话不多说,直接实操
第一步:
- 打开VS2022,新建一个C#控制台应用

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

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

第二步:
在我们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)

第三步:
在这里之后,就可以在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#中的代码块做一些改动,便于维护
在改动中我们将用到
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}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;
}
}
执行结果:
C#实现排序所耗时: 130ms
C实现排序所耗时:3ms
在实现本案例中,可能在编译后,大家所看到的结果不是很出乎意料,但这只是一种案例,希望通过此案例的分析,能给大家带来一些意想不到的收获叭。
最后
简单做一下总结叭,通过上述所描述的从第一步如何创建一个DLL到如何通过C#去调用的一个简单实例,也应该能给正在查阅相关资料的你有所收获,也希望能给在这方面有所研究的你有一些相关的启发,同时也希望能给目前对这方面毫无了解的你有一个更进一步的学习。
1、Visual Studio 2022
2、.net 6.0
P/Invok是什么?
P/Invoke全称为Platform Invoke(平台调用),其实际上就是一种函数调用机制,通过P/Invoke就可以实现调用非托管Dll中的函数。
在开始之前,我们首先需要了解C#中有关托管与非托管的区别
托管(Collocation),即在程序运行时会自动释放内存;
非托管,即在程序运行时不会自动释放内存。
废话不多说,直接实操
第一步:
打开VS2022,新建一个C#控制台应用
右击解决方案,添加一个新建项,新建一个"动态链接库(DLL)",新建完之后需要右击当前项目--> 属性 --> C/C++ --> 预编译头 --> 选择"不使用编译头"

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

第二步:
在我们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)

第三步:
在这里之后,就可以在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#中的代码块做一些改动,便于维护
在改动中我们将用到
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}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;
}
}
执行结果:
C#实现排序所耗时: 130ms
C实现排序所耗时:3ms
在实现本案例中,可能在编译后,大家所看到的结果不是很出乎意料,但这只是一种案例,希望通过此案例的分析,能给大家带来一些意想不到的收获叭。
最后
简单做一下总结叭,通过上述所描述的从第一步如何创建一个DLL到如何通过C#去调用的一个简单实例,也应该能给正在查阅相关资料的你有所收获,也希望能给在这方面有所研究的你有一些相关的启发,同时也希望能给目前对这方面毫无了解的你有一个更进一步的学习。
作者:百宝门-刘忠帅
原文地址:https://blog.baibaomen.com/p-invoke之c调用动态链接库dll/
P/Invoke之C#调用动态链接库DLL的更多相关文章
- BCB如何编写,调用动态链接库DLL
一 编写动态链接库DLL DLL简称动态链接库,是Windows中程序的重要组成部分.想象一下,一个程序需要多人共同完成开发,怎么个共同法?这时我们就要考虑把程序分为好几个模块,团队每一个成员开发一个 ...
- ASP.net/C#中如何调用动态链接库DLL
动态链接库(也称为DLL,即为“Dynamic Link Library”的缩写)是Microsoft Windows最重要的组成要素之一,打开Windows系统文件夹,你会发现文件夹中有很多DLL文 ...
- LR调用动态链接库DLL
什么是动态库? 动态库一般又叫动态链接库(DLL),是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库. 动态链接提供了一种方法 ,使进程可以 ...
- 可执行Jar包调用动态链接库(DLL/SO)
踩过了很多的坑,查了很多资料,在此记录一下,以SpringBoot项目为基础. Maven加入JNA依赖 <!-- JNA start --> <dependency> < ...
- JAVA调用动态链接库DLL之JNative学习
package com.ehfscliax; import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import ...
- JAVA调用动态链接库(dll)
菜鸡爬坑 基础知识 因为某个东西的keygen我只会在win下生成!! 所以只能出此下策!!之前一直是android下用jni调用so文件,现在试下java在win平台下调用dll 首先还是 ...
- 制作和unity调用动态链接库dll文件
首先用vc建立一个dll工程 然后在里面建立一个testunity.h文件.内容如下 1 extern "C" int _declspec(dllexport)testunity( ...
- VS2010编写动态链接库DLL及单元测试用例,调用DLL测试正确性
转自:http://blog.csdn.net/testcs_dn/article/details/27237509 本文将创建一个简单的动态链接库,并编写一个控制台应用程序使用该动态链接库,该动态链 ...
- VS2010编写动态链接库DLL和单元测试,转让DLL测试的正确性
本文将创建一个简单的动态库-link,谱写控制台应用程序使用该动态链接库,该动态链接库为"JAVA调用动态链接库DLL之JNative学习"中使用的DLL,仅仅是项目及文件名不同. ...
- 编译可供C#调用的C/C++动态链接库dll文件
编译可供C#调用的C/C++动态链接库dll文件,C语言控制台应用程序,探索生成dll过程 由于项目需求,需要公司另一个团队提供相关算法支持,是用C语言编译好的dll库提供给我们进行调用. 但是拿到d ...
随机推荐
- 前端自动化测试框架Cypress环境搭建
1. 下载安装node.js(之前的随笔里写过安装nodejs具体步骤这里不过多介绍了) 下载地址:https://nodejs.org/en/ 2.安装路径(这是安装完成nodejs的路径) 3. ...
- uniapp里面设置onlaunch以后在加载页面调接口
main.js 里面 Vue.prototype.$onLaunched = new Promise(resolve => { Vue.prototype.$isResolve = resolv ...
- Vue+Element+Table表格动态跨列文章
https://my.oschina.net/u/4772459/blog/4699602 如图所示: 1 <template class="SysRole"> 2 & ...
- SaltStack学习笔记
SaltStack三大功能: 1. 远程执行 2. 配置管理 (状态) 3.云管理 运维三板斧:监控.执行.配置 四种运行方式: 1.Local 2. Minion/Master C/S架构 3 ...
- 会议室NTP同步时钟布置与系统建设要领
深圳市立显电子有限公司,专业LED时钟生产厂家!--------[点击进入] NTP同步时钟之会议室设计要领: 会议室是企业必不可少的办公配套用房,一般分为大中小不同类型,有的企业中小会议室有多 ...
- @media移动PC端fontSize响应设置
@media only screen and (min-width: 320px) and (max-width: 359px) { html, body { font-size: 8.51px; } ...
- OSIDP-单处理器调度-09
处理器调度的类型 处理器调度的目的是为了满足系统的目标,将进程分配到处理器上执行. 系统并发度:正等待处理器处理的进程个数.(这里的表述和08里面的不同,以这里为准.主要是懒得改,见谅= =) 长程调 ...
- 彻底解决impdp还原数控时提示不让写日志的问题ORA-39064
之前通过impdp导入时一直没问题,突然今天导入时出现了问题,如下: 加权限.换空间.用其他用户均失败. 后来在网上找的大部分都是说字符集什么的,如果是字符集的问题那我之前导入应该就有问题,不可能 ...
- 11、jmeter配置元件--计数器
前面学过csv和变量 csv里面的数据是固定的 如果里面的数据不够 线程要么就停止要么就需要重头再来 不太灵活 用到固定化的数据,比如说多少个用户等等 如果有一些是变动的 随着线程数增加 数据 ...
- 什么是js柯里化(curry)?
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术. 举例来说,一个接收3个参数的普通函数,在进行柯里化后,柯里化版本的函数接收一个参数并返回接收下一个参数 ...