I have the following unmanaged function I need to call from C#:
TLINError __stdcall LIN_GetAvailableHardware(
HLINHW *pBuff,
WORD wBuffSize,
int *pCount
);
The library provides this C# P/Invoke definition:
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
HLINHW[] pBuff,
ushort wBuffSize,
out ushort pCount);
This function receives a C like array with its size defined via wBuffSize
. pCount
is the actual count of items written into the array. HLINHW
is an alias for ushort
. I was wondering if I could somehow avoid the allocation of a managed array. My first intuition was to modify the C# function definition to accept a Span<ushort>
and pass a stack allocated span as parameter.
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
Span<HLINHW> pBuff,
ushort wBuffSize,
out ushort pCount);
This failed with the following error: Cannot marshal 'parameter #1': Generic types cannot be marshaled
I can workaround it by changing the definition to:
[DllImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static extern TLINError GetAvailableHardware(
ref readonly HLINHW pBuff,
ushort wBuffSize,
out ushort pCount);
And call it like this:
ReadOnlySpan<ushort> buffer = stackalloc ushort[availableHardwareCount];
status = PLinApi.GetAvailableHardware(ref MemoryMarshal.GetReference(buffer), (ushort)(buffer.Length*sizeof(ushort)), out availableHardwareCount);
Is it possible to define the C# P/Invoke definition in such a way that it would accept a span directly? If so how can I do it? There should also still be a possibility to pass null into the unmanaged function.
If we ignore the slight inconvenience of calling MemoryMarshal.GetReference(buffer)
would such direct span P/Invoke benefit the resulting program in any way. Performance, Memory?
I have figured it out. There appears to be a new source generated P/Invoke option since .NET 7. It replaces DllImport. If we now define the P/Invoke function like following:
[LibraryImport("plinapi.dll", EntryPoint = "LIN_GetAvailableHardware")]
public static partial TLINError GetAvailableHardware(
ReadOnlySpan<HLINHW> pBuff,
ushort wBuffSize,
out ushort pCount);
We can call it simply with:
ReadOnlySpan<ushort> buffer = stackalloc ushort[availableHardwareCount];
PLinApi.GetAvailableHardware(buffer, (ushort)(buffer.Length*sizeof(ushort)),
out availableHardwareCount);
Or the null version:
PLinApi.GetAvailableHardware(ReadOnlySpan<ushort>.Empty, 0,
out var availableHardwareCount);
It works with zero allocations at the managed side.