Remarque
L’accès à cette page requiert une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page requiert une autorisation. Vous pouvez essayer de modifier des répertoires.
Le marshaling est le processus de transformation des types lorsqu’ils doivent passer d’un code managé à un code natif.
Le marshaling est nécessaire, car les types dans le code managé et non managé sont différents. Dans le code managé, par exemple, on dispose d’un string, tandis que les chaînes non managées peuvent être encodées en .NET string (UTF-16), en page de codes ANSI, en UTF-8, être terminées par un caractère null, en ASCII, etc. Par défaut, le sous-système P/Invoke cherche à appliquer le comportement approprié, comme décrit dans cet article. Toutefois, pour les situations où vous avez besoin d’un contrôle supplémentaire, vous pouvez utiliser l’attribut MarshalAs pour spécifier le type attendu du côté non managé. Par exemple, si vous souhaitez que la chaîne soit envoyée en tant que chaîne UTF-8 terminée par null, vous pouvez le faire comme suit :
[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPUTF8Str)] string parameter);
// or
[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);
Si vous appliquez l’attribut System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute à l’assembly, les règles de la section suivante ne s’appliquent pas. Pour plus d’informations sur la façon dont les valeurs .NET sont exposées au code natif lorsque cet attribut est appliqué, consultez disabled runtime marshalling.
Règles par défaut de marshaling des types courants
En règle générale, le runtime tente de prendre la bonne décision en matière de marshaling, c’est-à-dire celle qui demande le moins de travail de votre part. Les tableaux suivants décrivent comment chaque type est marshalé par défaut lorsqu’il est utilisé dans un paramètre ou un champ. Les types entiers et caractères de largeur fixe C99/C++11 sont utilisés pour s’assurer que le tableau suivant est correct pour toutes les plateformes. Vous pouvez utiliser n’importe quel type natif qui a les mêmes exigences d’alignement et de taille que ces types.
La première table décrit les correspondances de différents types pour lesquels le marshaling P/Invoke et le marshaling des champs sont identiques.
Important
Lors de l’appel d’une fonction C qui utilise long, utilisez CLong ou CULong (.NET 6+) au lieu de C# long. Pour plus d’informations et de solutions de contournement pour les versions antérieures de .NET, consultez Considérations relatives au type de données de plateforme croisée.
Note
Le type wchar_t est UTF-16 (2 octets) sur Windows mais est défini par le compilateur sur d’autres plateformes, généralement UTF-32 (4 octets) sur Linux et macOS. En raison de cela, il est difficile d’utiliser wchar_t* en tant qu’ABI multiplateforme unique. Lorsque vous concevez une API native multiplateforme, préférez char* avec un contrat d’encodage clairement défini (par exemple, UTF-8) au lieu de wchar_t*.
Note
Les chaînes natives char* utilisent l’encodage défini par la bibliothèque ou la plateforme. Lorsque vous appelez une fonction C qui accepte char*, pour correspondre à l'encodage attendu, choisissez l'option de marshaling de chaîne correcte, comme StringMarshalling.Utf8 pour UTF-8, StringMarshalling.Utf16 pour UTF-16 ou StringMarshalling.Custom pour d’autres encodages.
| Mot clé C# | type de .NET | Type natif |
|---|---|---|
byte |
System.Byte |
uint8_t |
sbyte |
System.SByte |
int8_t |
short |
System.Int16 |
int16_t |
ushort |
System.UInt16 |
uint16_t |
int |
System.Int32 |
int32_t |
uint |
System.UInt32 |
uint32_t |
long |
System.Int64 |
int64_t |
ulong |
System.UInt64 |
uint64_t |
char |
System.Char |
Soit char soit char16_t selon l’encodage du P/Invoke ou de la structure. Consultez la documentation de l’ensemble de caractères. |
System.Char |
Soit char* soit char16_t* selon l’encodage du P/Invoke ou de la structure. Consultez la documentation de l’ensemble de caractères. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
types pointeur .NET (par exemple, void*) |
void* |
|
Type dérivé de System.Runtime.InteropServices.SafeHandle |
void* |
|
Type dérivé de System.Runtime.InteropServices.CriticalHandle |
void* |
|
bool |
System.Boolean |
Type BOOL Win32 |
decimal |
System.Decimal |
Struct DECIMAL COM |
| délégué .NET | Pointeur de fonction natif | |
System.DateTime |
Type DATE Win32 |
|
System.Guid |
Type GUID Win32 |
Certaines catégories ont des valeurs par défaut différentes pour le marshaling comme paramètre ou comme structure.
| type de .NET | Type natif (paramètre) | Type natif (champ) |
|---|---|---|
| .NET tableau | Pointeur vers le début d’un tableau de représentations natives des éléments du tableau | Non autorisé sans [MarshalAs] attribut |
Classe avec LayoutKind de Sequential ou de Explicit |
Pointeur vers la représentation native de la classe | Représentation native de la classe |
Le tableau suivant inclut les règles de marshaling par défaut qui sont uniquement pour Windows. Sur les plateformes non Windows, vous ne pouvez pas marshaler ces types.
| type de .NET | Type natif (paramètre) | Type natif (champ) |
|---|---|---|
System.Object |
VARIANT |
IUnknown* |
System.Array |
Interface COM | Non autorisé sans [MarshalAs] attribut |
System.ArgIterator |
va_list |
Non autorisé |
System.Collections.IEnumerator |
IEnumVARIANT* |
Non autorisé |
System.Collections.IEnumerable |
IDispatch* |
Non autorisé |
System.DateTimeOffset |
int64_t représentant le nombre de cycles depuis le 1er janvier 1601 à minuit |
int64_t représentant le nombre de cycles depuis le 1er janvier 1601 à minuit |
Certains types peuvent uniquement être marshalés en tant que paramètres et non en tant que champs. Ces types sont répertoriés dans le tableau suivant :
| type de .NET | Type natif (paramètre uniquement) |
|---|---|
System.Text.StringBuilder |
char* ou char16_t* selon le CharSet du P/Invoke. Consultez la documentation de l’ensemble de caractères. |
System.ArgIterator |
va_list (sur Windows x86/x64/arm64 uniquement) |
System.Runtime.InteropServices.ArrayWithOffset |
void* |
System.Runtime.InteropServices.HandleRef |
void* |
Si ces valeurs par défaut ne font pas exactement ce que vous voulez, vous pouvez personnaliser la façon dont les paramètres sont marshalés. L’article de marshaling de paramètres vous guide tout au long de la façon dont différents types de paramètres sont marshalés.
Marshaling par défaut dans les scénarios COM
Lorsque vous appelez des méthodes sur des objets COM dans .NET, le runtime .NET modifie les règles de marshaling par défaut pour qu’elles correspondent à la sémantique COM courante. Le tableau suivant répertorie les règles qui .NET runtimes les utilise dans les scénarios COM :
| type de .NET | Type natif (appels de méthode COM) |
|---|---|
System.Boolean |
VARIANT_BOOL |
StringBuilder |
LPWSTR |
System.String |
BSTR |
| Types délégués |
_Delegate* dans .NET Framework. Interdit dans .NET Core et .NET 5+. |
System.Drawing.Color |
OLECOLOR |
| tableau .NET | SAFEARRAY |
System.String[] |
SAFEARRAY sur BSTR |
Marshaling de classes et de structures
Un autre aspect du marshaling de types consiste à passer une structure à une méthode non managée. Par exemple, certaines des méthodes non managées nécessitent un struct en tant que paramètre. Dans ces cas, vous devez créer un struct correspondant ou une classe dans une partie gérée du monde pour l’utiliser comme paramètre. Toutefois, la définition de la classe n’est pas suffisante, vous devez également indiquer au marshaller comment mapper les champs de la classe au struct non managé. Ici, l’attribut StructLayout devient utile.
using System;
using System.Runtime.InteropServices;
Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);
Console.WriteLine(systemTime.Year);
internal static partial class Win32Interop
{
[LibraryImport("kernel32.dll")]
internal static partial void GetSystemTime(out SystemTime systemTime);
[StructLayout(LayoutKind.Sequential)]
internal ref struct SystemTime
{
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Millisecond;
}
}
Le code précédent montre un exemple simple d'appel d'une fonction GetSystemTime(). La partie intéressante se trouve à la ligne 13. L’attribut spécifie que les champs de la classe doivent être mappés séquentiellement au struct de l’autre côté (non managé). Cela signifie que le nommage des champs n’est pas important, seul son ordre est important, car il doit correspondre au struct non managé, illustré dans l’exemple suivant :
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
Il est possible que le marshaling par défaut de votre structure ne vous convienne pas. L’article Personnalisation de l'assemblage de structure vous apprend à personnaliser la manière dont votre structure est assemblée.