C#通过PInvoke调用C++函数的备忘录
什么是PInvoke
PInvoke是Platform Invoke的缩写,是.NET Framework提供给C#程序员调用非托管DLL(Dynamic Link Library)在 Windows 平台上的接口技术。PInvoke 提供的主要技术便是 Marshal 类,Marshal 类可以完成 数据类型 转换、内存管理等工作,使得 C# 能够正确的调用非托管 DLL中的函数以及使用数据。
C++函数在C#中的使用
在C#中默认情况下只能使用.NET Framework中提供的类库及开发的类库。但有时候我们需要使用非托管的dll库(主要是C/C++的动态库)。在这种情况下,我们可以使用PInvoke技术来实现对非托管库的调用。
比如,下面例子我们要调用一个C++的函数:
extern "C"{
__declspec(dllexport) int Add(int a, int b)
{
return a + b;
}
}
这个函数是通过__declspec(dllexport)
修饰导出,目的是在被其他程序调用的时候可以被找到。
编写一个C#的程序调用这个C++函数:
using System;
using System.Runtime.InteropServices;
namespace PInvokeDemo
{
class Program
{
[DllImport("test.dll", EntryPoint = "Add", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
static extern int Add(int a, int b);
static void Main(string[] args)
{
int result = Add(1, 2);
Console.WriteLine(result);
Console.ReadKey();
}
}
}
重点介绍DllImport
属性
- DllImport属性的常用属性说明
- EntryPoint:对应C++导出方法的方法名,如果与C++中方法名相同可以省略。注意:C#不支持C++的函数重载,查询导出方法名时需要区分名称。
- SetLastError:表示方法调用后,调用GetLastError函数可以获取扩展的错误信息。
- CharSet:字符集,指明字符集的类型,防止出现字符混乱的问题。C++字符集是MBCS类型。Ansi占用1个字节,Unicode占用2个字节。
- ExactSpelling:字符匹配。
- CallingConvention:使用的调用约定,默认为 STDCALL。其他的还有[Cdecl]和[Thiscall]等约定方式。
注意点
- 确保将生成的DLL文件路径与应用程序在同一个目录中,或者添加一个引用到该DLL文件。
- 确保你已经正确包含了
System.Runtime.InteropServices
命名空间,用于访问DllImportAttribute。
C++结构体在C#中的使用
在C++的dll中也有结构体,结构体中定义了很多值和方法。如何来将这个结构体导入到C#中来使用呢?
为了展示,我们新添一个C++的代码示例:
typedef struct tagBookInfo {
char BookName[50];
int BookID;
}BookInfo;
extern "C" {
__declspec(dllexport) int ChangeBookInfo(BookInfo* pbookinfo, int bookid, const char* newname)
{
if (!pbookinfo)
return 0;
if (pbookinfo->BookID == bookid)
{
strncpy_s(pbookinfo->BookName,sizeof(pbookinfo->BookName), newname, strlen(newname));
return 1;
}
else
{
return 0;
}
}
}
这个函数为一个改变图书信息的函数。通过传入一个BookInfo
结构体指针(BookInfo*
)来改变指定ID的图书的名称(newname
)。
对应C#中的代码:
using System;
using System.Runtime.InteropServices;
namespace PInvokeDemo
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct BookInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string BookName;
public int BookID;
}
class Program
{
[DllImport("test.dll", EntryPoint = "ChangeBookInfo", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern int ChangeBookInfo(ref BookInfo pbookinfo, int bookid, [MarshalAs(UnmanagedType.LPTStr)] String newname);
static void Main(string[] args)
{
BookInfo mybook = new();
mybook.BookID = 100;
mybook.BookName = "book_name";
Console.WriteLine("Before ChangeBookInfo, myBookName is " + mybook.BookName);
if(ChangeBookInfo(ref mybook, 100, "new book name") == 1)
{
Console.WriteLine("After ChangeBookInfo, myBookName is " + mybook.BookName);
}
else
{
Console.WriteLine("Fail to change book info.");
}
Console.ReadKey();
}
}
}
重点介绍StructLayout
属性
- StructLayout属性的常用属性说明:
- LayoutKind:指定如何布置类或结构,有三种枚举
- Sequential:在内存中顺序排列每个字段,每个字段偏移量+当前长度 = 下一个字段偏移量。
- Explicit:自定义布局。
- Auto:让程序根据需要自动进行调整布局。
- CharSet:字符集,指明字符集的类型,防止出现字符混乱的问题。C++字符集是MBCS类型。Ansi占用1个字节,Unicode占用2个字节。
- FieldOffset:可以指定字段的偏移量,一个结构体一个一个字段地抽象出相对应的值。
输出结果:
Before ChangeBookInfo, myBookName is book_name
After ChangeBookInfo, myBookName is new book name
以上就是通过PInvoke技术调用C++的函数,以及C++结构体导入C#中的说明和示例。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#通过PInvoke调用c++函数的备忘录的实例详解 - Python技术站