Marshaling de types
Le marshaling est le processus qui consiste à transformer les types quand ils doivent naviguer entre du code managé et du code natif.
La raison pour laquelle le marshaling est nécessaire est que les types diffèrent entre le code managé et le code non managé. Dans le code managé, par exemple, vous avez un string
, tandis que les chaînes non managées peuvent être l’encodage .NET string
(UTF-16), l’encodage de page de codes ANSI, UTF-8, se terminant par null, ASCII, etc. Par défaut, le sous-système P/Invoke tente de prendre la bonne décision en fonction du comportement par défaut, décrit dans cet article. Toutefois, dans les cas où vous avez besoin de plus de contrôle, vous pouvez employer l’attribut MarshalAs pour spécifier le type attendu du côté du code non managé. Par exemple, pour que la chaîne soit envoyée sous forme de chaîne UTF-8 terminée par null, vous pouvez procéder ainsi :
[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] 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 Marshaling de runtime désactivé.
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 indiquent comment chaque type est marshalé par défaut lorsqu’il est utilisé dans un paramètre ou un champ. Les types de caractères et d’entiers de longueur fixe C99/C ++11 garantissent que le tableau suivant est correct pour toutes les plateformes. Vous pouvez utiliser n’importe quel type natif ayant les mêmes exigence 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.
Mot clé C# | Type .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 |
char ou char16_t selon l’encodage de P/Invoke ou la structure. Voir la documentation charset. |
System.Char |
char* ou char16_t* selon l’encodage de P/Invoke ou la structure. Voir la documentation charset. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
Types pointeur .NET (p. ex. 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 .NET | Type natif (paramètre) | Type natif (champ) |
---|---|---|
Tableau .NET | Pointeur vers le début d’un tableau de représentations natives des éléments du tableau | Non autorisé sans attribut [MarshalAs] |
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 présente les règles de marshaling par défaut propres à Windows. Sur les autres plateformes, il n’est pas possible de marshaler ces types.
Type .NET | Type natif (paramètre) | Type natif (champ) |
---|---|---|
System.Object |
VARIANT |
IUnknown* |
System.Array |
Interface COM | Non autorisé sans attribut [MarshalAs] |
System.ArgIterator |
va_list |
Non autorisé |
System.Collections.IEnumerator |
IEnumVARIANT* |
Non autorisé |
System.Collections.IEnumerable |
IDispatch* |
Non autorisé |
System.DateTimeOffset |
représentant le nombre de cycles depuis le 1er janvier 1601 à minuit | représentant le nombre de cycles depuis le 1er janvier 1601 à minuit |
Certains types ne peuvent être marshalés que comme paramètres, et non comme champs :
Type .NET | Type natif (paramètre uniquement) |
---|---|
System.Text.StringBuilder |
char* ou char16_t* selon le CharSet de P/Invoke. Voir la documentation charset. |
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 vous conviennent pas tout à fait, vous pouvez personnaliser la façon dont les paramètres sont marshalés. L’article Marshaling des paramètres explique comment faire, pour différents types de paramètres.
Marshaling par défaut dans les scénarios COM
Quand 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 liste les règles utilisées par les runtimes .NET dans les scénarios COM :
Type .NET | Type natif (appels de méthodes COM) |
---|---|
System.Boolean |
VARIANT_BOOL |
StringBuilder |
LPWSTR |
System.String |
BSTR |
Types délégués | _Delegate* dans le .NET Framework. Non autorisé dans .NET Core et .NET 5+. |
System.Drawing.Color |
OLECOLOR |
Tableau .NET | SAFEARRAY |
System.String[] |
SAFEARRAY de BSTR s |
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 une structure comme paramètre. Il faut dans ce cas créer une classe ou un struct correspondant dans la partie managée de l’environnement pour l’utiliser comme paramètre. Toutefois, il ne suffit pas de définir la classe : il est également nécessaire d’indiquer au marshaleur comment mapper des champs de la classe au struct non managé. C’est ici qu’intervient l’attribut StructLayout
.
[LibraryImport("kernel32.dll")]
static partial void GetSystemTime(out SystemTime systemTime);
[StructLayout(LayoutKind.Sequential)]
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;
}
public static void Main(string[] args)
{
SystemTime st = new SystemTime();
GetSystemTime(st);
Console.WriteLine(st.Year);
}
Le code précédent illustre de manière simple les appels dans la fonction GetSystemTime()
. L’élément digne d’intérêt se trouve sur la ligne 4. L’attribut spécifie que les champs de la classe doivent être mappés séquentiellement à la structure de l’autre côté (non managé). Cela signifie que le nom des champs n’a pas d’importance ; seul leur ordre compte, car il doit correspondre au struct non managé, comme 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;
Il est possible que le marshaling par défaut de votre structure ne vous convienne pas. L’article Personnaliser le marshaling des structures explique comment personnaliser la façon dont les structures sont marshalées.