Skip to content

[API Proposal]: Implementations of marshallers using CustomTypeMarshallerAttribute #66623

Closed
@elinor-fung

Description

@elinor-fung

Background and motivation

For source-generated p/invokes, #46838 and #66121 were recently approved to support custom marshalling via types marked with System.Runtime.InteropServices.CustomTypeMarshallerAttribute and conforming to a specific shape. This issue proposes adding implementations of marshallers that the p/invoke source generator would be able use.

API Proposal

Strings:

[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
    Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling )]
public unsafe ref struct AnsiStringMarshaller
{
    public AnsiStringMarshaller(string? str);
    public AnsiStringMarshaller(string? str, Span<byte> buffer);

    public byte* ToNativeValue();
    public void FromNativeValue(byte* value);

    public string? ToManaged();

    public void FreeNative();
}

[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
    Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling )]
public unsafe ref struct Utf8StringMarshaller
{
    public Utf8StringMarshaller(string? str);
    public Utf8StringMarshaller(string? str, Span<byte> buffer);

    public byte* ToNativeValue();
    public void FromNativeValue(byte* value);

    public string? ToManaged();

    public void FreeNative();
}

[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
    Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling)]
public unsafe ref struct Utf16StringMarshaller
{
    public Utf16StringMarshaller(string? str);
    public Utf16StringMarshaller(string? str, Span<ushort> buffer);

    public ref ushort GetPinnableReference();

    public ushort* ToNativeValue();
    public void FromNativeValue(ushort* value);

    public string? ToManaged();

    public void FreeNative();
}

Arrays:

[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder[]),
    CustomTypeMarshallerKind.LinearCollection, BufferSize = 0x200,
    Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling)]
public unsafe ref struct ArrayMarshaller<T>
{
    public ArrayMarshaller(int sizeOfNativeElement);
    public ArrayMarshaller(T[]? array, int sizeOfNativeElement);
    public ArrayMarshaller(T[]? array, Span<byte> buffer, 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 T[]? ToManaged();

    public void FreeNative();
}

[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*[]),
    CustomTypeMarshallerKind.LinearCollection, BufferSize = 0x200,
    Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling)]
public unsafe ref struct PtrArrayMarshaller<T> where T : unmanaged
{
    public PtrArrayMarshaller(int sizeOfNativeElement);
    public PtrArrayMarshaller(T*[]? array, int sizeOfNativeElement);
    public PtrArrayMarshaller(T*[]? array, Span<byte> buffer, int sizeOfNativeElement);

    public ReadOnlySpan<IntPtr> GetManagedValuesSource();

    public Span<IntPtr> 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 T*[]? ToManaged();

    public void FreeNative();
}

API Usage

The p/invoke source generator would use these marshallers for marshalling some types without requiring explicit user specification of the custom marshaller. The marshallers could also be explicitly used with the LibraryImportAttribute.StringMarshallingCustomType property or the MarshalUsing and NativeMarshalling attributes.

[LibraryImport("NativeLib")]
internal static partial void Method([MarshalUsing(typeof(AnsiStringMarshaller))] string str);

[LibraryImport("NativeLib", StringMarshallingCustomType = typeof(AnsiStringMarshaller))]
internal static partial string Method(string s1, string s2);

Alternative Designs

The p/invoke source generator could inject marshallers / emit the code directly instead of using public marshaller types. However, that would be more of a size impact than having the types in runtime libraries. It would also mean that any issues in sensitive areas like string and array marshalling would require users to recompile to regenerate code, rather than being fixed by a newer runtime.

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions