Runtime: Question: P/Invoke and passing Span as parameters

Created on 8 Aug 2018  路  3Comments  路  Source: dotnet/runtime

After reading https://github.com/dotnet/coreclr/issues/14460, https://medium.com/@antao.almada/p-invoking-using-span-t-a398b86f95d3 and some Gitter discussion I am still not sure about the correct pattern for passing Spans to native code.

The general gist seems to be that if you know stackalloc was used (or any other non-movable memory) you can safely do:

```c#
[DllImport(LibName, EntryPoint = "sum")]
public static extern int Sum(in int buffer, UIntPtr size);

ReadOnlySpan span = stackalloc int[bufferSize];
return Native.Sum(MemoryMarshal.GetReference(span), (UIntPtr)span.Length);


However, when movable memory is involved the pattern is less clear. Article linked above suggests `fixed` block around `MemoryMarshal.GetReference(span)`:

```c#
ReadOnlySpan<int> span = new int[bufferSize];
fixed (int* ptr = &MemoryMarshal.GetReference(span))
{
    return Native.Sum(Unsafe.AsRef<int>(ptr), (UIntPtr)span.Length);
}

This pattern is used in some CoreFX interop code. Some other interop code omits the fixed block though:

```c#
internal static bool DsaSign(SafeDsaHandle dsa, ReadOnlySpan hash, int hashLength, Span refSignature, out int outSignatureLength) =>
DsaSign(dsa, ref MemoryMarshal.GetReference(hash), hashLength, ref MemoryMarshal.GetReference(refSignature), out outSignatureLength);

    [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaSign")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DsaSign(SafeDsaHandle dsa, ref byte hash, int hashLength, ref byte refSignature, out int outSignatureLength);

```

Where does the above code get the guarantee that the memory is not moved by the garbage collector?

question

Most helpful comment

PInvoke marshaling will pin the pointer for ref byte hash arguments that will pin the whole block as side-effect.

All 3 comments

The preferred pattern is to use unmanaged pointer in the PInvoke signature and use fixed block to call it:

fixed (int* ptr = span)
{
    return Native.Sum(ptr, span.Length);
}

This pattern best describes the intent and avoids corner case bugs that the other patterns suffer from.

Some of your other examples work too, but they obfuscate the intent.

Thanks, that really shows the intent quite clearly.

How about the second question about the last mentioned pattern? Is there some guarantee that using MemoryMarshal.GetReference(hash) directly in a ref/in/out parameter will work? The fact that there's no explicit fixed makes it non-obvious how it guarantees that GC doesn't move the memory.

PInvoke marshaling will pin the pointer for ref byte hash arguments that will pin the whole block as side-effect.

Was this page helpful?
0 / 5 - 0 ratings