Search code examples
c#.netpinvokemarshalling

.NET P/Invoke marshalling with spans instead of managed arrays


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?


Solution

  • 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.