Génération de source pour le marshaling personnalisé

.NET 7 introduit un nouveau mécanisme de personnalisation de la façon dont un type est marshalé lors de l’utilisation de l’interopérabilité générée par la source. Le générateur source pour P/Invokes reconnaît MarshalUsingAttribute et NativeMarshallingAttribute en tant qu’indicateurs pour le marshaling personnalisé d’un type.

NativeMarshallingAttribute peut être appliqué à un type pour indiquer le marshaling personnalisé par défaut pour ce type. Le MarshalUsingAttribute peut être appliqué à un paramètre ou à une valeur de retour pour indiquer le marshaling personnalisé pour cette utilisation particulière du type, et est prioritaire par rapport à tout NativeMarshallingAttribute qui peut se trouver sur le type lui-même. Ces deux attributs attendent un Type, le type de marshaleur de point d’entrée, marqué avec un ou plusieurs attributs CustomMarshallerAttribute. Chaque CustomMarshallerAttribute indique quelle implémentation de marshaleur doit être utilisée pour marshaler le type managé spécifié pour le MarshalMode spécifié.

Implémentation du marshaleur

Les implémentations de marshaleur peuvent être sans état ou avec état. Si le type de marshaleur est une classe static, il est considéré comme sans état. S’il s’agit d’un type valeur, il est considéré comme avec état, et une instance de ce marshaleur sera utilisée pour marshaler un paramètre spécifique ou une valeur de retour. Différentes formes pour l’implémentation du marshaleur sont attendues selon qu’un marshaleur est sans état ou avec état et qu’il prend en charge le marshaling de managé à non managé, non managé à managé, ou les deux. Le SDK .NET inclut des analyseurs et des correcteurs de code pour faciliter l’implémentation de marshaleurs conformes aux formes requises.

MarshalMode

Le MarshalMode spécifié dans un CustomMarshallerAttribute détermine la prise en charge et la forme de marshaling attendues pour l’implémentation du marshaleur. Tous les modes prennent en charge les implémentations de marshaleur sans état. Les modes de marshaling d’éléments ne prennent pas en charge les implémentations de marshaleur avec état.

MarshalMode Prise en charge attendue Peut être avec état
ManagedToUnmanagedIn Managé à non managé Oui
ManagedToUnmanagedRef Managé à non managé et non managé à managé Oui
ManagedToUnmanagedOut Non managé à managé Oui
UnmanagedToManagedIn Non managé à managé Oui
UnmanagedToManagedRef Managé à non managé et non managé à managé Oui
UnmanagedToManagedOut Managé à non managé Oui
ElementIn Managé à non managé Non
ElementRef Managé à non managé et non managé à managé Non
ElementOut Non managé à managé Non

MarshalMode.Default indique que l’implémentation du marshaleur doit être utilisée pour tout mode qu’elle prend en charge. Si une implémentation de marshaleur pour un MarshalMode plus spécifique est également spécifiée, elle est prioritaire sur MarshalMode.Default.

Utilisation de base

Nous pouvons spécifier NativeMarshallingAttribute sur un type, pointant vers un type de marshaleur de point d’entrée qui est une classe static ou un struct.

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

ExampleMarshaller, le type de marshaleur de point d’entrée, est marqué avec CustomMarshallerAttribute, pointant vers un type d’implémentation du marshaleur. Dans cet exemple, ExampleMarshaller est à la fois le point d’entrée et l’implémentation. Il est conforme aux formes de marshaleur attendues pour le marshaling personnalisé d’une valeur.

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

Le ExampleMarshaller dans l’exemple est un marshaleur sans état qui implémente la prise en charge du marshaling de managé à non managé et de non managé à managé. La logique de marshaling est entièrement contrôlée par votre implémentation du marshaleur. Le marquage de champs sur un struct avec MarshalAsAttribute n’a aucun effet sur le code généré.

Le type Example peut ensuite être utilisé dans la génération de source P/Invoke. Dans l’exemple P/Invoke suivant, ExampleMarshaller est utilisé pour marshaler le paramètre de managé à non managé. Il sera également utilisé pour marshaler la valeur de retour de non managée à managée.

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

Pour utiliser un autre marshaleur pour une utilisation spécifique du type Example, spécifiez MarshalUsingAttribute sur le site d’utilisation. Dans l’exemple P/Invoke suivant, ExampleMarshaller est utilisé pour marshaler le paramètre de managé à non managé. OtherExampleMarshaller sera utilisé pour marshaler la valeur de retour de non managée à managée.

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

Regroupements

Appliquez le ContiguousCollectionMarshallerAttribute à un type de point d’entrée du marshaleur pour indiquer qu’il s’agit de collections contiguës. Le type doit avoir un paramètre de type de plus que le type managé associé. Le dernier paramètre de type est un espace réservé et sera rempli par le générateur source avec le type non managé pour le type d’élément de la collection.

Par exemple, vous pouvez spécifier un marshaling personnalisé pour un List<T>. Dans le code suivant, ListMarshaller est à la fois le point d’entrée et l’implémentation. Il est conforme aux formes de marshaleur attendues pour le marshaling personnalisé d’une collection.

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

Le ListMarshaller dans l’exemple est un marshaleur de collection sans état qui implémente la prise en charge du marshaling de managé à non managé et de non managé à managé pour un List<T>. Dans l’exemple P/Invoke suivant, ListMarshaller est utilisé pour marshaler le paramètre de managé à non managé et pour marshaler la valeur de retour de non managée à managée. CountElementName indique que le paramètre numValues doit être utilisé comme nombre d’éléments lors du marshaling de la valeur de retour de non managée à managée.

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

Voir aussi