-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
One of the goals of the source-generated marshalling in .NET 7 is to support modern types at interop boundaries. In particular, there has been significant interest in using Span<T> and ReadOnlySpan<T> at interop boundaries. This API proposal includes a number of different Span-based marshallers. This proposal does not include defining any of these marshaller types as the "default" marshallers for span types; however, we can choose to define some of these as defaults if we so desire.
SpanMarshaller<T>andReadOnlySpanMarshaller<T>. These types marshal the span values in the same style as arrays. However, since theSpantypes treat an empty span as identical to a null span, these types passnullto native code when an empty span is passed as the managed value.NeverNullSpanMarshaller<T>andNeverNullReadOnlySpanMarshaller<T>. These types marshal the span values similarly toSpanMarshaller<T>andReadOnlySpanMarshaller<T>, but when an empty ornullspan is passed as a value, the marshaller passes a non-nullvalue to native code that can be dereferenced but should not be written to. This allows developers to opt-in to similar behavior as arrays (where we don't passnullwhen the input is an empty array, but instead pass a reference to where the zeroth element would live).
We also propose updating the source generator to recognize a static GetPinnableReference method of the same shape as an extension GetPinnableReference method on the marshaller type that takes the managed type. The marshallers will implement this pattern to provide a faster path for by-value P/Invoke scenarios without requiring them to be specified as the default marshallers for the types.
It seems that we want to make the ReadOnlySpanMarshaller and SpanMarshaller types the default marshallers for their respective types. If we want to do so, then we'll add [NativeMarshalling] attributes to the managed types pointing at these marshallers.
API Proposal
namespace System.Runtime.InteropServices.Marshalling
{
[CustomTypeMarshaller(typeof(ReadOnlySpan<>), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)]
public unsafe ref struct ReadOnlySpanMarshaller<T>
{
public ReadOnlySpanMarshaller(int sizeOfNativeElement);
public ReadOnlySpanMarshaller(ReadOnlySpan<T> managed, int sizeOfNativeElement);
public ReadOnlySpanMarshaller(ReadOnlySpan<T> managed, Span<byte> stackSpace, int sizeOfNativeElement);
public ReadOnlySpan<T> GetManagedValuesSource();
public Span<byte> GetNativeValuesDestination();
public ref byte GetPinnableReference();
public byte* ToNativeValue();
public void FreeNative();
public static ref T GetPinnableReference(ReadOnlySpan<T> managed);
}
[CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)]
public unsafe ref struct SpanMarshaller<T>
{
public SpanMarshaller(int sizeOfNativeElement);
public SpanMarshaller(Span<T> managed, int sizeOfNativeElement);
public SpanMarshaller(Span<T> managed, Span<byte> stackSpace, int sizeOfNativeElement);
public ReadOnlySpan<T> GetManagedValuesSource();
public Span<T> GetManagedValuesDestination(int length);
public Span<byte> GetNativeValuesDestination();
public ReadOnlySpan<byte> GetNativeValuesSource(int length);
public ref byte GetPinnableReference();
public byte* ToNativeValue();
public void FromNativeValue(byte* value);
public static ref T GetPinnableReference(Span<T> managed);
public Span<T> ToManaged();
public void FreeNative();
}
[CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)]
public unsafe ref struct NeverNullSpanMarshaller<T>
{
public NeverNullSpanMarshaller(int sizeOfNativeElement);
public NeverNullSpanMarshaller(Span<T> managed, int sizeOfNativeElement);
public NeverNullSpanMarshaller(Span<T> managed, Span<byte> stackSpace, int sizeOfNativeElement);
public ReadOnlySpan<T> GetManagedValuesSource();
public Span<T> GetManagedValuesDestination(int length);
public Span<byte> GetNativeValuesDestination();
public ReadOnlySpan<byte> GetNativeValuesSource(int length);
public ref byte GetPinnableReference();
public byte* ToNativeValue();
public void FromNativeValue(byte* value);
public static ref T GetPinnableReference(Span<T> managed);
public Span<T> ToManaged();
public void FreeNative();
}
[CustomTypeMarshaller(typeof(ReadOnlySpan<>), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)]
public unsafe ref struct NeverNullReadOnlySpanMarshaller<T>
{
public NeverNullReadOnlySpanMarshaller(int sizeOfNativeElement);
public NeverNullReadOnlySpanMarshaller(ReadOnlySpan<T> managed, int sizeOfNativeElement);
public NeverNullReadOnlySpanMarshaller(ReadOnlySpan<T> managed, Span<byte> stackSpace, int sizeOfNativeElement);
public ReadOnlySpan<T> GetManagedValuesSource();
public Span<byte> GetNativeValuesDestination();
public ref byte GetPinnableReference();
public byte* ToNativeValue();
public static ref T GetPinnableReference(ReadOnlySpan<T> managed);
public void FreeNative();
}
}API Usage
[LibraryImport("MyNativeLib")]
static partial int SumValues([MarshalUsing(typeof(SpanMarshaller<int>))] Span<int> values, int numValues);Alternative Designs
No response
Risks
As these are new marshallers, we can choose to use different allocators for the native memory if we so choose. However, if we choose an allocator different from the array marshallers, then we break compatibility between array marshallers and span marshallers in the by-ref case (ref Span<T> vs ref T[]). We could add an additional INativeAllocator interface and implementations for our various allocators, and add a second generic parameter to the span marshallers (and the array marshallers if we desire as they haven't shipped yet in an official release) to allow developers to pass in the allocator to use.