Quellgenerierung für benutzerdefiniertes Marshalling

.NET 7 führt einen neuen Mechanismus für die Anpassung der Art des Marshallings eines Typs ein, wenn von der Quelle generiertes Interop verwendet wird. Der Quellgenerator für P/Invokes erkennt MarshalUsingAttribute und NativeMarshallingAttribute als Indikatoren für das benutzerdefinierte Marshalling eines Typs.

NativeMarshallingAttribute kann auf einen Typ angewendet werden, um das standardmäßige benutzerdefinierte Marshalling für diesen Typ anzugeben. Das MarshalUsingAttribute kann auf einen Parameter oder Rückgabewert angewendet werden, um das benutzerdefinierte Marshalling für diese bestimmte Verwendung des Typs anzugeben, und hat dabei Vorrang vor jedem NativeMarshallingAttribute, das sich möglicherweise auf dem Typ selbst befindet. Beide Attribute erwarten einen Type, den Einstiegspunkt-Marshallertyp, der mit einem oder mehreren CustomMarshallerAttribute-Attributen gekennzeichnet ist. Jedes CustomMarshallerAttribute gibt an, welche Marshallerimplementierung verwendet werden sollte, um den angegebenen verwalteten Typ für den angegebenen MarshalMode zu marshallen.

Marshallerimplementierung

Marshallerimplementierungen können entweder zustandslos oder zustandsbehaftet sein. Wenn der Marshallertyp eine static Klasse ist, wird er als zustandslos betrachtet. Wenn es sich um einen Werttyp handelt, gilt er als zustandsbehaftet, und eine Instanz dieses Marshallers wird verwendet, um einen bestimmten Parameter oder Rückgabewert zu marshallen. Unterschiedliche Formen für die Marshallerimplementierung werden erwartet, je nachdem, ob ein Marshaller zustandslos oder zustandsbehaftet ist und ob er das Marshalling von verwaltet zu nicht verwaltet, nicht verwaltet zu verwaltet oder beides unterstützt. Das .NET SDK enthält Analysetools und Codekorrekturen, die die Implementierung von Marshallern unterstützen, die den erforderlichen Formen entsprechen.

MarshalMode

Der in einem CustomMarshallerAttribute angegebene MarshalMode bestimmt die erwartete Marshallingunterstützung und Form für die Marshallerimplementierung. Alle Modi unterstützen zustandslose Marshallerimplementierungen. Elementmarshallingmodi unterstützen keine zustandsbehafteten Marshallerimplementierungen.

MarshalMode Erwartete Unterstützung Kann zustandsbehaftet sein
ManagedToUnmanagedIn Verwaltet zu nicht verwaltet Ja
ManagedToUnmanagedRef Verwaltet zu nicht verwaltet und nicht verwaltet zu verwaltet Ja
ManagedToUnmanagedOut Nicht verwaltet zu verwaltet Ja
UnmanagedToManagedIn Nicht verwaltet zu verwaltet Ja
UnmanagedToManagedRef Verwaltet zu nicht verwaltet und nicht verwaltet zu verwaltet Ja
UnmanagedToManagedOut Verwaltet zu nicht verwaltet Ja
ElementIn Verwaltet zu nicht verwaltet Nein
ElementRef Verwaltet zu nicht verwaltet und nicht verwaltet zu verwaltet Nein
ElementOut Nicht verwaltet zu verwaltet Nein

MarshalMode.Default gibt an, dass die Marshallerimplementierung für jeden unterstützten Modus verwendet werden sollte. Wenn auch eine Marshallerimplementierung für einen spezifischeren MarshalMode angegeben ist, hat er Vorrang vor MarshalMode.Default.

Grundlegende Verwendung

Wir können NativeMarshallingAttribute für einen Typ angeben, auf einen Einstiegspunkt-Marshallertyp verweisend, der entweder eine static-Klasse oder ein struct ist.

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

Der Einstiegspunkt-Marshallertyp ExampleMarshaller ist mit CustomMarshallerAttribute gekennzeichnet, auf einen Marshallerimplementierungstyp verweisend. In diesem Beispiel ist ExampleMarshaller sowohl der Einstiegspunkt als auch die Implementierung. Dies entspricht Marshallerformen, die für das benutzerdefinierte Marshalling eines Werts erwartet werden.

[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;
    }
}

Der ExampleMarshaller im Beispiel ist ein zustandsloser Marshaller, der Unterstützung für das Marshalling von verwaltet zu nicht verwaltet und von nicht verwaltet zu verwaltet implementiert. Die Marshallinglogik wird vollständig von Ihrer Marshallerimplementierung gesteuert. Das Markieren von Feldern in einer Struktur mit MarshalAsAttribute hat keine Auswirkung auf den generierten Code.

Der Example-Typ kann dann in der P/Invoke-Quellgenerierung verwendet werden. Im folgenden P/Invoke-Beispiel wird ExampleMarshaller verwendet, um den Parameter von verwaltet zu nicht verwaltet zu marshallen. Er wird auch verwendet, um den Rückgabewert von nicht verwaltet zu verwaltet zu marshallen.

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

Um einen anderen Marshaller für eine bestimmte Verwendung des Example-Typs zu verwenden, geben Sie MarshalUsingAttribute am Verwendungsstandort an. Im folgenden P/Invoke-Beispiel wird ExampleMarshaller verwendet, um den Parameter von verwaltet zu nicht verwaltet zu marshallen. OtherExampleMarshaller wird verwendet, um den Rückgabewert von nicht verwaltet zu verwaltet zu marshallen.

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

Sammlungen

Wenden Sie das ContiguousCollectionMarshallerAttribute auf einen Marshallereinstiegspunkttyp an, um anzugeben, dass es sich um zusammenhängende Sammlungen handelt. Der Typ muss einen Typparameter mehr als der zugeordnete verwaltete Typ aufweisen. Der letzte Typparameter ist ein Platzhalter und wird vom Quellgenerator mit dem nicht verwalteten Typ für den Elementtyp der Sammlung ausgefüllt.

Sie können z. B. benutzerdefiniertes Marshalling für eine List<T> angeben. Im folgenden Code ist ListMarshaller sowohl der Einstiegspunkt als auch die Implementierung. Er entspricht Marshallerformen, die für das benutzerdefinierte Marshalling einer Sammlung erwartet werden.

[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();
}

Der ListMarshaller im Beispiel ist ein zustandsloser Sammlungsmarshaller, der Unterstützung für das Marshalling von verwaltet zu nicht verwaltet und von nicht verwaltet zu verwaltet für eine List<T> implementiert. Im folgenden P/Invoke-Beispiel wird ListMarshaller verwendet, um den Parameter von verwaltet zu nicht verwaltet zu marshallen und den Rückgabewert von nicht verwaltet zu verwaltet zu marshallen. CountElementName gibt an, dass der numValues-Parameter beim Marshalling des Rückgabewerts von nicht verwaltet zu verwaltet als Elementanzahl verwendet werden soll.

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

Weitere Informationen