在 .NET 开发中,我们经常需要与用 C/C++ 编写的本机(Native)代码库进行交互。平台调用(Platform Invoke,简称 P/Invoke)是 .NET 提供的一种强大机制,允许托管代码(如 C#)调用非托管函数(如 DLL 中的函数)。
本文将介绍两种主要的 P/Invoke 方法,并演示如何处理复杂的数据结构。
方法 1:传统 [DllImport]
[DllImport]
是自 .NET Framework 时代以来最经典的 P/Invoke 方式。它通过一个属性来声明对 DLL 中函数的引用。
1
2
3
|
// 使用 DllImport 属性指定 DLL 名称和函数入口点
[DllImport("ZrqHRV.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "func")]
public static extern int func(int devtype);
|
方法 2:现代 [LibraryImport]
(适用于 .NET 7+)
从 .NET 7 开始,推荐使用 [LibraryImport]
属性。它利用源生成器(Source Generator)在编译时生成封送代码,相比 [DllImport]
的运行时生成,具有更高的性能和更好的调试体验。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// .NET 7+ 的新方法,性能更优
[LibraryImport("nativelib", EntryPoint = "to_lower", StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);
// 配合 MarshalAs 进行更精细的封送控制
[LibraryImport("nativelib", EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLowerWithMarshalAs([MarshalAs(UnmanagedType.LPWStr)] string str);
// 指定调用约定 (Calling Convention)
[LibraryImport("nativelib", EntryPoint = "to_lower", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
internal static partial string ToLowerWithCallConv(string str);
|
定义结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
[StructLayout(LayoutKind.Sequential)]
public struct ParamsIn
{
public int deviceId; //设备号
public int packageSn; //包序号
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)]
public int[] ecgList; //心电波形,200个点
public int ecgFlag; //心电导联告警标识 0:正常 1:脱落
public int ecgFs = 200; //心电采样率:200
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public int[] chData;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public int[] abData;
public int rspFlag;
//public int hrThresholdHi; //心率高阈值120
//public int hrThresholdLo; //心率低阈值:50
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public int[] xList; //x轴数据
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public int[] yList; //y轴数据
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public int[] zList; //z轴数据
public ParamsIn()
{
}
}
|
传递结构体并解析结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
ParamsIn paramsIn = new()
{
ecgList = new int[200],
deviceId = 1,
packageSn = sn,
ecgFlag = 0,
ecgFs = 200,
//hrThresholdLo = 120,
//hrThresholdHi = 50
};
// 分配非托管内存
IntPtr pntIn = Marshal.AllocHGlobal(Marshal.SizeOf(paramsIn));
IntPtr pntOut = Marshal.AllocHGlobal(Marshal.SizeOf<ParamsOut>());
// 托管对象到非托管对象
Marshal.StructureToPtr(paramsIn, pntIn, false);
// 方法参数为指针类型(nint paramsIn, nint paramsOut)
bool b = NewProtocolLibrary.processSignal(pntIn, pntOut);
if (b)
{
// 非托管对象到托管对象
ParamsOut? paramsOut = (ParamsOut?)Marshal.PtrToStructure(pntOut, typeof(ParamsOut));
if (paramsOut.HasValue)
{
heartRate = paramsOut.Value.hr;
foreach (var item in paramsOut.Value.ecgfilterList)
{
channel.Writer.TryWrite((short)item);
}
}
}
//释放非托管内存
Marshal.FreeHGlobal(pntIn);
Marshal.FreeHGlobal(pntOut);
|