Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
.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.
Marshaller-Implementierung
Benutzerdefinierte Marshallerimplementierungen können entweder zustandslos oder zustandsbehaftet sein. Wenn der Marshallertyp eine static Klasse ist, wird er als zustandslos betrachtet, und die Implementierungsmethoden sollten den Zustand nicht über Aufrufe hinweg nachverfolgen. Wenn es sich um einen Werttyp handelt, wird er als zustandsbehaftet betrachtet, und eine Instanz dieses Marshallers wird verwendet, um einen spezifischen Parameter oder Rückgabewert zu verarbeiten. Die Verwendung einer eindeutigen Instanz ermöglicht die Erhaltung des Zustands über den Marshallingprozess und das Rückgängigmachen des Marshallingprozesses.
Marshaller-Formen
Der Satz von Methoden, die der Marshalling-Generator von einem benutzerdefinierten Marshallertyp erwartet, wird als Marshaller-Shape bezeichnet. Zur Unterstützung zustandsloser, statischer benutzerdefinierter Marshallertypen in .NET Standard 2.0 (die keine statischen Schnittstellenmethoden unterstützt) und die Leistung zu verbessern, werden Schnittstellentypen nicht zum Definieren und Implementieren der Marshaller-Shapes verwendet. Stattdessen werden die Formen im Artikel " Benutzerdefinierte Marshaller-Shapes " dokumentiert. Die erwarteten Methoden (oder Formen) hängen davon ab, ob der Marshaller zustandslos oder zustandsbehaftet ist und ob das Marshalling (von verwaltet zu nicht verwaltet, von nicht verwaltet zu verwaltet oder beides) unterstützt wird (deklariert mit CustomMarshallerAttribute.MarshalMode). Das .NET SDK enthält Analysegeräte und Codekorrekturen, um bei der Implementierung von Marshallern zu helfen, die den erforderlichen Shapes 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 |
Verwenden Sie diese Methode MarshalMode.Default , um anzugeben, dass die Marshallerimplementierung auf jeden unterstützten Modus angewendet wird, basierend auf den implementierten Methoden. Wenn Sie einen Marshaller für einen spezifischeren MarshalModeWert angeben, hat dieser Marshaller Vorrang vor einem, der als Defaultgekennzeichnet ist.
Grundlegende Nutzung
Marshalling eines einzelnen Werts
Um einen benutzerdefinierten Marshaller für einen Typ zu erstellen, müssen Sie einen Einstiegspunkt-Marshallertyp definieren, der die erforderlichen Marshallmethoden implementiert. Der Einstiegspunkt-Marshaller-Typ kann entweder eine static-Klasse oder ein struct sein und muss mit CustomMarshallerAttribute gekennzeichnet werden.
Betrachten Sie beispielsweise einen einfachen Typ, den Sie zwischen verwaltetem und nicht verwaltetem Code marshallen möchten:
public struct Example
{
public string Message;
public int Flags;
}
Definieren des Marshallertyps
Sie können einen Typen namens ExampleMarshaller erstellen, der mit CustomMarshallerAttribute gekennzeichnet ist, um anzugeben, dass es sich um den Einstiegspunkt-Marshaller-Typ mit benutzerdefinierten Marshalling-Informationen für den Typ Example handelt. Das erste Argument für CustomMarshallerAttribute ist der verwaltete Typ, auf den der Marshaller gerichtet ist. Das zweite Argument ist der vom Marshaller unterstützte MarshalMode. Das dritte Argument ist der Marshallertyp selbst, d. h. der Typ, der die Methoden in der erwarteten Form implementiert.
[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static unsafe class ExampleMarshaller
{
public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
{
return new ExampleUnmanaged()
{
Message = (IntPtr)Utf8StringMarshaller.ConvertToUnmanaged(managed.Message),
Flags = managed.Flags
};
}
public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
{
return new Example()
{
Message = Utf8StringMarshaller.ConvertToManaged((byte*)unmanaged.Message),
Flags = unmanaged.Flags
};
}
public static void Free(ExampleUnmanaged unmanaged)
{
Utf8StringMarshaller.Free((byte*)unmanaged.Message);
}
internal struct ExampleUnmanaged
{
public IntPtr Message;
public int Flags;
}
}
Der hier gezeigte ExampleMarshaller implementiert zustandsloses Marshalling aus dem verwalteten Typ Example zu einer blitfähigen Repräsentation in dem Format, das der native Code erwartet (ExampleUnmanaged) – und zurück. Die Methode Free wird verwendet, um alle nicht verwalteten Ressourcen freizugeben, die im Rahmen des Marshallingprozesses zugeordnet wurden. Die Marshallinglogik wird vollständig von der Marshallerimplementierung gesteuert. Das Markieren von Feldern in einer Struktur mit MarshalAsAttribute hat keine Auswirkungen auf den generierten Code.
ExampleMarshaller Hier sehen Sie sowohl den Einstiegspunkttyp als auch den Implementierungstyp. Bei Bedarf können Sie das Marshalling jedoch für verschiedene Modi anpassen, indem Sie separate Marshallertypen für jeden Modus erstellen. Fügen Sie einen neuen CustomMarshallerAttribute für jeden Modus wie in der folgenden Klasse hinzu. In der Regel ist dies nur für zustandsbehaftete Marshaller erforderlich, wenn als Marshallertyp struct verwendet wird und dieser Typ den Zustand aufrufübergreifend beibehält. Üblicherweise werden die Implementierungstypen im Einstiegspunkt-Marshallertyp geschachtelt.
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedIn, typeof(ExampleMarshaller.ManagedToUnmanagedIn))]
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedOut, typeof(ExampleMarshaller.UnmanagedToManagedOut))]
internal static class ExampleMarshaller
{
internal struct ManagedToUnmanagedIn
{
public void FromManaged(TManaged managed) => throw new NotImplementedException();
public TNative ToUnmanaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException()
}
internal struct UnmanagedToManagedOut
{
public void FromUnmanaged(TNative unmanaged) => throw new NotImplementedException();
public TManaged ToManaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException();
}
}
Deklarieren des zu verwendenden Marshallers
Nachdem Sie den Marshallertyp erstellt haben, können Sie das MarshalUsingAttribute in der Interop-Methodensignatur verwenden, um anzugeben, dass Sie diesen Marshaller für einen bestimmten Parameter oder Rückgabewert nutzen möchten. MarshalUsingAttribute nimmt den Einstiegspunkt-Marshallertyp (in diesem Fall ExampleMarshaller) als Argument an.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Um zu vermeiden, dass der Marshallertyp bei jeder Verwendung des Typs Example angegeben werden muss, können Sie NativeMarshallingAttribute auch direkt auf den Typ Example anwenden. Dies gibt an, dass der angegebene Marshaller standardmäßig für alle Verwendungen des Typs in der Example Interop-Quellgenerierung verwendet werden soll.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
Der Example Typ kann dann in quellgenerierten P/Invoke-Methoden verwendet werden, ohne den Marshallertyp anzugeben. 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);
Wenn Sie einen anderen Marshaller für einen bestimmten Parameter oder Rückgabewert des Example Typs verwenden möchten, geben Sie MarshalUsingAttribute an der Verwendungsstelle 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);
Marshalling-Sammlungen
Nicht generische Auflistungen
Für Auflistungen, die nicht generisch für den Typ des Elements sind, sollten Sie einen einfachen Marshallertyp erstellen, wie zuvor gezeigt.
Generische Auflistungen
Um einen benutzerdefinierten Marshaller für einen generischen Sammlungstyp zu erstellen, können Sie das ContiguousCollectionMarshallerAttribute Attribut verwenden. Dieses Attribut gibt an, dass der Marshaller für zusammenhängende Auflistungen wie Arrays oder Listen bestimmt ist und eine Reihe von Methoden bereitstellt, die der Marshaller implementieren muss, um das Marshalling der Elemente der Auflistung zu unterstützen. Der Elementtyp der Auflistung, für die das Marshalling erfolgt, muss ebenfalls über einen mit den oben beschriebenen Verfahren für diesen Typ definierten Marshaller verfügen.
Wenden Sie das ContiguousCollectionMarshallerAttribute auf einen Marshallereinstiegspunkttyp an, um anzugeben, dass es sich um zusammenhängende Sammlungen handelt. Der Marshaller-Einstiegspunkttyp muss über einen weiteren Typparameter verfügen als der zugeordnete verwaltete Typ. 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 ListMarshaller handelt es sich sowohl um den Einstiegspunkt als auch um die Implementierung. Es entspricht einer der Marshaller-Formen, die für das benutzerdefinierte Marshalling einer Kollektion erwartet werden. (Beachten Sie, dass es sich um ein unvollständiges Beispiel handelt.)
[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>.DefaultMarshaller))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
public static class DefaultMarshaller
{
public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
{
numElements = managed.Count;
nuint collectionSizeInBytes = managed.Count * /* size of T */;
return (byte*)NativeMemory.Alloc(collectionSizeInBytes);
}
public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
=> new Span<TUnmanagedElement>((TUnmanagedElement*)unmanaged, numElements);
public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
=> new List<T>(length);
public static Span<T> GetManagedValuesDestination(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>((TUnmanagedElement*)nativeValue, numElements);
public static void Free(byte* unmanaged)
=> NativeMemory.Free(unmanaged);
}
}
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 das Marshalling des Auflistungscontainers für den Parameter von verwaltet zu nicht verwaltet und anschließend das Marshalling des Auflistungscontainers für den Rückgabewert von nicht verwaltet zu verwaltet vorzunehmen. Der Quellgenerator generiert Code, um die Elemente aus dem Parameter list in den vom Marshaller bereitgestellten Container zu kopieren. Da int blitfähig ist, ist für die Elemente selbst kein Marshalling erforderlich. CountElementName gibt an, dass der Parameter numValues beim Marshalling des Rückgabewerts von nicht verwaltet zu verwaltet als Elementanzahl verwendet werden soll.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
Wenn der Elementtyp der Auflistung ein benutzerdefinierter Typ ist, können Sie den Elementmarshaller für diesen Typ mit einem zusätzlichen MarshalUsingAttribute mit ElementIndirectionDepth = 1 angeben.
ListMarshaller verarbeitet den Auflistungscontainer, und ExampleMarshaller übernimmt das Marshalling jedes Elements von nicht verwaltet zu verwaltet und umgekehrt. ElementIndirectionDepth gibt an, dass der Marshaller auf die Elemente der Auflistung anzuwenden ist, die eine Ebene tiefer als die Auflistung selbst angeordnet sind.
[LibraryImport("nativelib")]
[MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
List<Example> list,
out int numValues);