Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
.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 marshaleurs personnalisés peuvent être sans état ou avec état. Si le type marshaller est une static classe, il est considéré comme sans état et les méthodes d’implémentation ne doivent pas suivre l’état entre les appels. S'il s'agit d'un type de valeur, il est considéré comme ayant un état, et une instance de ce marshaller sera utilisée pour traiter un paramètre ou une valeur de retour spécifique. L’utilisation d’une instance unique permet de conserver l’état dans le processus de marshaling et d’unmarshaling.
Formes de Marshaller
L’ensemble de méthodes que le générateur de marshalling attend d’un type marshaller personnalisé est appelé structure de marshaller. Pour prendre en charge les types de marshaller personnalisés statiques et sans état dans .NET Standard 2.0 (qui ne prend pas en charge les méthodes d’interface statiques), et afin d'améliorer les performances, les types d’interface ne sont pas utilisés pour définir et réaliser les structures de marshaller. À la place, les formes sont documentées dans l’article Formes de marshaleur personnalisé . Les méthodes attendues (ou forme) varient selon que le marshaleur est sans état ou avec état, et qu’il prend en charge le marshaling de code managé en code non managé, de code non managé en code managé ou les deux (déclaré avec CustomMarshallerAttribute.MarshalMode). Le Kit de développement logiciel (SDK) .NET inclut des analyseurs et des fixateurs de code pour faciliter l’implémentation de marshallers 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 |
Utilisez MarshalMode.Default pour indiquer que l’implémentation du marshaller s’applique à n’importe quel mode pris en charge, en fonction des méthodes qu’il implémente. Si vous spécifiez un marshaleur pour un MarshalMode plus spécifique, ce marshaleur prévaut sur celui marqué comme Default.
Utilisation de base
Marshaling d’une valeur unique
Pour créer un marshaleur personnalisé pour un type, vous devez définir un type de marshaleur de point d’entrée qui implémente les méthodes de marshaling requises. Le type de marshaleur de point d’entrée peut être une classe static ou un struct et il doit être marqué avec CustomMarshallerAttribute.
Par exemple, envisagez un type simple que vous souhaitez transférer entre le code géré et non géré :
public struct Example
{
public string Message;
public int Flags;
}
Définir le type de marshaleur
Vous pouvez créer un type appelé ExampleMarshaller qui est marqué avec CustomMarshallerAttribute pour indiquer qu’il constitue le type de marshaleur de point d’entrée fournissant des informations de marshaling personnalisées pour le type Example. Le premier argument du CustomMarshallerAttribute est le type managé que le marshaleur cible. Le deuxième argument est le MarshalMode pris en charge par le marshaleur. Le troisième argument est le type de marshaleur lui-même, c’est-à-dire le type qui implémente les méthodes dans la forme attendue.
[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;
}
}
Le ExampleMarshaller illustré ici implémente le marshaling sans état du type Example managé vers une représentation blittable au format attendu par le code natif (ExampleUnmanaged) et inversement. La Free méthode est utilisée pour libérer toutes les ressources non managées allouées pendant le processus de marshaling. La logique de marshaling est entièrement contrôlée par l’implémentation du marshaleur. Le marquage de champs sur un struct n’a MarshalAsAttribute aucun effet sur le code généré.
ExampleMarshaller Voici le type de point d’entrée et le type d’implémentation. Toutefois, si nécessaire, vous pouvez personnaliser la sérialisation pour différents modes en créant des types de sérialiseur distincts pour chaque mode. Ajoutez un nouveau CustomMarshallerAttribute pour chaque mode comme dans la classe suivante. En général, cela n’est nécessaire que pour les marshaleurs avec état, où le type de marshaleur est un struct qui maintient l’état sur l’ensemble des appels. Par convention, les types d’implémentations sont imbriqués dans le type de marshaleur de point d’entrée.
[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();
}
}
Déclarer le marshaleur à utiliser
Une fois que vous avez créé le type marshaller, vous pouvez utiliser la MarshalUsingAttribute signature de la méthode d’interopérabilité pour indiquer que vous souhaitez utiliser ce marshaller pour un paramètre ou une valeur de retour spécifique. Le MarshalUsingAttribute prend le type de marshaleur de point d’entrée comme argument, dans ce cas ExampleMarshaller.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Pour éviter d’avoir à spécifier le type de marshaleur pour chaque utilisation du type Example, vous pouvez également appliquer le NativeMarshallingAttribute au type Example lui-même. Cela indique que le marshaleur spécifié doit être utilisé par défaut pour toutes les utilisations du type Example dans la génération de source d’interopérabilité.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
Le Example type peut ensuite être utilisé dans les méthodes P/Invoke générées par la source sans spécifier le type marshaller. Dans l’exemple P/Invoke suivant, ExampleMarshaller est utilisé pour marshaler le paramètre de managé à non managé. Il sera également utilisé pour convertir la valeur de retour d’un environnement non managé à un environnement managé.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Pour utiliser un autre marshaller pour un paramètre ou une valeur de retour spécifique du Example type, 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 permettra de marshaler la valeur de retour du code non managé au code managé.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Marshaling de collections
Collections non génériques
Pour les collections non génériques sur le type de l’élément, vous devez créer un type de marshaleur simple comme indiqué précédemment.
Collections génériques
Pour créer un marshaller personnalisé pour un type de collection générique, vous pouvez utiliser l’attribut ContiguousCollectionMarshallerAttribute . Cet attribut indique que le marshaleur est destiné aux collections contiguës, comme des tableaux ou des listes, et fournit un ensemble de méthodes que le marshaleur doit implémenter pour prendre en charge le marshaling des éléments de la collection. Le type d’élément de la collection marshalée doit également avoir un marshaleur défini pour cela à l’aide des méthodes décrites précédemment.
Appliquez le ContiguousCollectionMarshallerAttribute à un type de point d’entrée du marshaleur pour indiquer qu’il s’agit de collections contiguës. Le type de marshaleur de point d’entrée doit avoir un paramètre de type de plus que le type managé associé. Le dernier paramètre de type sert d'espace réservé et sera complété par le générateur source avec le type non managé correspondant au 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 il s’agit à la fois du point d’entrée et de l’implémentation. Il est conforme à l’une des formes de marshaleur attendues pour le marshaling personnalisé d’une collection. (Notez qu’il s’agit d’un exemple incomplet.)
[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);
}
}
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 permettra de marshaler le conteneur de collection pour le paramètre du code géré vers le code non géré et de marshaler le conteneur de collection pour la valeur de retour du code non géré vers le code géré. Le générateur source génère du code pour copier les éléments du paramètre list vers le conteneur fourni par le marshaller. Comme int est blittable, les éléments eux-mêmes n’ont pas besoin d’être marshalés. CountElementName indique que le paramètre numValues doit être utilisé comme nombre d'éléments lors de la conversion de la valeur de retour de non managé à managé.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
Lorsque le type d’élément de la collection est un type personnalisé, vous pouvez spécifier le marshaleurr d’élément pour cela à l’aide d’un MarshalUsingAttribute supplémentaire avec ElementIndirectionDepth = 1.
Le ListMarshaller gérera le conteneur de collection et ExampleMarshaller marshalera chaque élément du code non managé vers le code managé et inversement. Le ElementIndirectionDepth indique que le marshaleur doit être appliqué aux éléments de la collection, qui sont un niveau plus profond que la collection elle-même.
[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);