Dela via


Källgenerering för anpassad marshalling

.NET 7 introducerar en ny mekanism för anpassning av hur en typ är ordnad när du använder källgenererad interop. Källgeneratorn för P/Invokes identifierar MarshalUsingAttribute och NativeMarshallingAttribute som indikatorer för anpassad sortering av en typ.

NativeMarshallingAttribute kan tillämpas på en typ för att ange standard anpassad marshalling för den typen. MarshalUsingAttribute Kan tillämpas på en parameter eller ett returvärde för att ange anpassad sortering för den specifika användningen av typen, med företräde framför alla NativeMarshallingAttribute som kan finnas på själva typen. Båda dessa attribut förväntar sig en Type– startpunkts marshallertypen – som är markerad med ett eller flera CustomMarshallerAttribute attribut. Var CustomMarshallerAttribute och en anger vilken marshaller-implementering som ska användas för att konvertera den angivna hanterade typen för den angivna MarshalMode.

Marshaller-implementering

Marshaller-implementeringar kan antingen vara tillståndslösa eller tillståndskänsliga. Om marshallertypen är en static klass anses den vara tillståndslös. Om det är en värdetyp anses den vara tillståndskänslig och en instans av den marshallern används för att konvertera en specifik parameter eller ett returvärde. Olika former för marshaller-implementeringen förväntas baserat på om en marshaller är tillståndslös eller tillståndskänslig och om den stöder marshalling från hanterad till ohanterad, ohanterad till hanterad eller både och. .NET SDK innehåller analysverktyg och kodkorrigeringar som hjälper dig att implementera marshallers som överensstämmer med de former som krävs.

MarshalMode

Angivet MarshalMode i en CustomMarshallerAttribute avgör det förväntade marshalling-stödet och formen för marshaller-implementeringen. Alla lägen stöder tillståndslösa marshallerimplementeringar. Element marshalling-lägen stöder inte tillståndskänsliga marshallerimplementeringar.

MarshalMode Förväntat stöd Kan vara tillståndskänslig
ManagedToUnmanagedIn Hanterad till ohanterad Ja
ManagedToUnmanagedRef Hanterad till ohanterad och ohanterad till hanterad Ja
ManagedToUnmanagedOut Ohanterad till hanterad Ja
UnmanagedToManagedIn Ohanterad till hanterad Ja
UnmanagedToManagedRef Hanterad till ohanterad och ohanterad till hanterad Ja
UnmanagedToManagedOut Hanterad till ohanterad Ja
ElementIn Hanterad till ohanterad Nej
ElementRef Hanterad till ohanterad och ohanterad till hanterad Nej
ElementOut Ohanterad till hanterad Nej

MarshalMode.Default anger att marshaller-implementeringen ska användas för alla lägen som den stöder. Om en marshaller-implementering för en mer specifik MarshalMode anges har den företräde framför MarshalMode.Default.

Grundläggande användning

Vi kan ange NativeMarshallingAttribute på en typ som pekar på en startpunkts marshallertyp som antingen är en static klass eller en struct.

[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
    public string Message;
    public int Flags;
}

ExampleMarshaller, är startpunktens marshallertyp markerad med CustomMarshallerAttribute, som pekar på en marshaller-implementeringstyp . I det här exemplet ExampleMarshaller är både startpunkten och implementeringen. Den överensstämmer med marshallerformer som förväntas för anpassad marshalling av ett värde.

[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static class ExampleMarshaller
{
    public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
        => throw new NotImplementedException();

    public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    public static void Free(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    internal struct ExampleUnmanaged
    {
        public IntPtr Message;
        public int Flags;
    }
}

I ExampleMarshaller exemplet finns en tillståndslös marshaller som implementerar stöd för marshalling från hanterad till ohanterad och från ohanterad till hanterad. Marshalling-logiken styrs helt av din marshaller-implementering. Att markera fält på en struct med MarshalAsAttribute har ingen effekt på den genererade koden.

Typen Example kan sedan användas i P/Invoke-källgenerering. I följande P/Invoke-exempel ExampleMarshaller används för att konvertera parametern från hanterad till ohanterad. Det används också för att konvertera returvärdet från ohanterat till hanterat.

[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);

Om du vill använda en annan marshaller för en specifik användning av Example typen anger du MarshalUsingAttribute på användningsplatsen. I följande P/Invoke-exempel ExampleMarshaller används för att konvertera parametern från hanterad till ohanterad. OtherExampleMarshaller används för att konvertera returvärdet från ohanterat till hanterat.

[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);

Samlingar

ContiguousCollectionMarshallerAttribute Använd på en marshaller-startpunktstyp för att ange att den är för sammanhängande samlingar. Typen måste ha en fler typparameter än den associerade hanterade typen. Den sista typparametern är en platshållare och fylls i av källgeneratorn med den ohanterade typen för samlingens elementtyp.

Du kan till exempel ange anpassad marshalling för en List<T>. I följande kod ListMarshaller är både startpunkten och implementeringen. Den överensstämmer med marshallerformer som förväntas för anpassad marshalling av en samling.

[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
    public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
        => throw new NotImplementedException();

    public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
        => throw new NotImplementedException();

    public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
        => throw new NotImplementedException();

    public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
        => throw new NotImplementedException();

    public static Span<T> GetManagedValuesDestination(List<T> managed)
        => throw new NotImplementedException();

    public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
        => throw new NotImplementedException();

    public static void Free(byte* unmanaged)
        => throw new NotImplementedException();
}

I ListMarshaller exemplet finns en tillståndslös samlings marshaller som implementerar stöd för marshalling från hanterad till ohanterad och från ohanterad till hanterad för en List<T>. I följande P/Invoke-exempel ListMarshaller används för att konvertera parametern från hanterad till ohanterad och för att konvertera returvärdet från ohanterat till hanterat. CountElementName anger att parametern numValues ska användas som elementantal när du samlar returvärdet från ohanterat till hanterat.

[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
    [MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
    out int numValues);

Se även