我需要从 C# 调用以下非托管函数:
TLINError __stdcall LIN_GetAvailableHardware(
HLINHW *pBuff,
WORD wBuffSize,
int *pCount
);
该库提供了此 C# P/Invoke 定义:
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
HLINHW[] pBuff,
ushort wBuffSize,
out ushort pCount);
此函数接收一个类似 C 的数组,其大小通过
wBuffSize
定义。 pCount
是写入数组的实际项目数。 HLINHW
是 ushort
的别名。我想知道是否可以以某种方式避免分配托管数组。我的第一直觉是修改 C# 函数定义以接受 Span<ushort>
并传递堆栈分配的跨度作为参数。
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
Span<HLINHW> pBuff,
ushort wBuffSize,
out ushort pCount);
此操作失败,并出现以下错误:无法封送“参数#1”:无法封送通用类型
我可以通过将定义更改为来解决这个问题:
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
ref readonly HLINHW pBuff,
ushort wBuffSize,
out ushort pCount);
并这样称呼它:
ReadOnlySpan<ushort> buffer = stackalloc ushort[availableHardwareCount];
status = PLinApi.GetAvailableHardware(ref MemoryMarshal.GetReference(buffer), (ushort)(buffer.Length*sizeof(ushort)), out availableHardwareCount);
是否可以以直接接受跨度的方式定义 C# P/Invoke 定义?如果是这样我该怎么办?还应该仍然有可能将 null 传递到非托管函数中。
如果我们忽略调用
MemoryMarshal.GetReference(buffer)
带来的轻微不便,这种直接跨度 P/Invoke 会对生成的程序产生任何好处。性能、内存?
我已经想通了。自 .NET 7 以来,似乎有一个新的源生成的 P/Invoke 选项。它取代了 DllImport。如果我们现在定义 P/Invoke 函数如下:
[LibraryImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static partial TLINError GetAvailableHardware(
ReadOnlySpan<HLINHW> pBuff,
ushort wBuffSize,
out ushort pCount);
我们可以简单地调用它:
ReadOnlySpan<ushort> buffer = stackalloc ushort[availableHardwareCount];
PLinApi.GetAvailableHardware(buffer, (ushort)(buffer.Length*sizeof(ushort)),
out availableHardwareCount);
或者空版本:
PLinApi.GetAvailableHardware(ReadOnlySpan<ushort>.Empty, 0,
out var availableHardwareCount);