Partager via


Marshaling par défaut pour les types valeur

Mise à jour : novembre 2007

La plupart des types valeur, tels que les nombres entiers et à virgule flottante, sont blittables et ne nécessitent pas de marshaling. Les autres types non blittables possèdent des représentations différentes en mémoire managée et non managée et nécessitent d'être marshalés. Toutefois, d'autres types nécessitent une mise en forme explicite au-delà des limites d'interopérabilité.

Cette rubrique fournit les informations suivantes sur les types valeur mis en forme :

  • Types valeur utilisés dans l'appel de code non managé

  • Types valeur utilisés dans COM Interop

En plus de décrire les types mis en forme, cette rubrique identifie les Types valeur système dont le comportement de marshaling est inhabituel.

Un type mis en forme est un type complexe qui contient des informations qui contrôlent explicitement la disposition de ses membres en mémoire. Les informations de disposition des membres sont fournies à l'aide de l'attribut StructLayoutAttribute. La disposition peut correspondre à l'une des valeurs d'énumération LayoutKind suivantes :

  • LayoutKind.Automatic

    Indique que le Common Language Runtime est libre de réorganiser les membres du type pour gagner en efficacité. Cependant, lorsqu'un type valeur est passé à du code non managé, la disposition des membres est prévisible. Toute tentative visant à marshaler une telle structure provoque automatiquement la levée d'une exception.

  • LayoutKind.Sequential

    Indique que les membres du type doivent être disposés en mémoire non managée dans le même ordre dans lequel ils apparaissent dans la définition des types managée.

  • LayoutKind.Explicit

    Indique que les membres sont disposés selon l'attribut FieldOffsetAttribute fourni avec chaque champ.

Types valeur utilisés dans l'appel de code non managé

Dans l'exemple suivant, les types Point et Rect fournissent des informations de disposition des membres à l'aide de StructLayoutAttribute.

Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
   Public x As Integer
   Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
   <FieldOffset(0)> Public left As Integer
   <FieldOffset(4)> Public top As Integer
   <FieldOffset(8)> Public right As Integer
   <FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}   

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
   [FieldOffset(0)] public int left;
   [FieldOffset(4)] public int top;
   [FieldOffset(8)] public int right;
   [FieldOffset(12)] public int bottom;
}

Lorsqu'ils sont marshalés vers du code non managé, ces types mis en forme sont marshalés en tant que structures de style C. Il s'agit ainsi d'une solution simple pour appeler une API non managée qui possède des arguments de structure. Par exemple, les structures POINT et RECT peuvent être passées à la fonction PtInRect de l'API Win32 de Microsoft comme suit :

BOOL PtInRect(const RECT *lprc, POINT pt);

Vous pouvez passer des structures à l'aide de la définition d'appel de code non managé suivante :

Class Win32API    
   Declare Auto Function PtInRect Lib "User32.dll" _
    (ByRef r As Rect, p As Point) As Boolean
End Class
class Win32API {
   [DllImport("User32.dll")]
   public static extern Bool PtInRect(ref Rect r, Point p);
}

Le type valeur Rect doit être passé par référence car l'API non managée attend un pointeur vers un RECT qui doit être passé à la fonction. Le type valeur Point est passé par valeur car l'API non managée s'attend à ce que le POINT soit passé dans la pile. Cette différence subtile est très importante. Les références sont passées au code non managé en tant que pointeurs. Les valeurs sont passées au code non managé dans la pile.

Remarque :

Lorsqu'un type mis en forme est marshalé en tant que structure, seuls les champs à l'intérieur du type sont accessibles. Si le type possède des méthodes, des propriétés, ou des événements, ceux-ci sont inaccessibles à partir du code managé.

Les classes peuvent également être marshalées vers le code non managé comme structures de style C à condition qu'elles possèdent une disposition de membre fixe. Les informations de disposition des membres pour une classe sont également fournies à l'aide de l'attribut StructLayoutAttribute. La principale différence entre les types valeur à disposition fixe et les classes à disposition fixe réside dans leur mode de marshaling vers le code non managé. Les types valeur sont passés par valeur (dans la pile) et par conséquent tous les changements apportés aux membres du type par l'appelé ne sont pas vus par l'appelant. Les types référence sont passés par référence (une référence au type est passée dans la pile) ; de ce fait, tous les changements apportés par l'appelé aux membres de types blittables d'un type sont vus par l'appelant.

Remarque :

Si un type référence possède des membres de types non blittables, la conversion est requise deux fois : la première fois lorsqu'un argument est passé au côté non managé, la deuxième fois lors du retour de l'appel. En raison de cette charge mémoire supplémentaire, les paramètres en entrée/sortie doivent être explicitement appliqués à un argument si l'appelant veut voir les changements effectués par l'appelé.

Dans l'exemple suivant, la classe SystemTime possède une disposition de membre séquentielle et peut être passée à la fonction GetSystemTime de l'API Win32.

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
   Public wYear As System.UInt16
   Public wMonth As System.UInt16
   Public wDayOfWeek As System.UInt16
   Public wDay As System.UInt16
   Public wHour As System.UInt16
   Public wMinute As System.UInt16
   Public wSecond As System.UInt16
   Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
   public ushort wYear; 
   public ushort wMonth;
   public ushort wDayOfWeek; 
   public ushort wDay; 
   public ushort wHour; 
   public ushort wMinute; 
   public ushort wSecond; 
   public ushort wMilliseconds; 
}

La fonction GetSystemTime est définie comme suit :

void GetSystemTime(SYSTEMTIME* SystemTime);

La définition d'appel de code non managé équivalente pour GetSystemTime est la suivante :

Public Class Win32
   Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (ByVal sysTime _
   As SystemTime)
End Class
class Win32API {
   [DllImport("Kernel32.dll", CharSet=CharSet.Auto)]
   public static extern void GetSystemTime(SystemTime st);
}

Remarquez que l'argument SystemTime n'est pas de type référence car SystemTime est une classe, pas un type valeur. À la différence des types valeur, les classes sont toujours passées par référence.

L'exemple de code suivant illustre une classe Point différente qui possède une méthode appelée SetXY. Du fait de la disposition séquentielle du type, celui-ci peut être passé à du code non managé et marshalé en tant que structure. Toutefois, le membre SetXY ne peut pas être appelé à partir du code non managé, même si l'objet est passé par référence.

<StructLayout(LayoutKind.Sequential)> Public Class Point
   Private x, y As Integer
   Public Sub SetXY(x As Integer, y As Integer)
      Me.x = x
      Me.y = y
   End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
   int x, y;
   public void SetXY(int x, int y){ 
      this.x = x;
      this.y = y;
   }
}

Types valeur utilisés dans COM Interop

Les types mis en forme peuvent être également passés aux appels de méthode COM interop. En fait, lorsqu'ils sont exportés dans une bibliothèque de types, les types valeur sont convertis automatiquement en structures. Comme l'illustre l'exemple suivant, le type valeur Point devient une définition de type (typedef) dont le nom est Point. Toutes les références au type valeur Point qui apparaissent ailleurs dans la bibliothèque de types sont remplacées par Point typedef.

Représentation de la bibliothèque de types

typedef struct tagPoint {
   int x;
   int y;
} Point;
interface _Graphics {
   …
   HRESULT SetPoint ([in] Point p)
   HRESULT SetPointRef ([in,out] Point *p)
   HRESULT GetPoint ([out,retval] Point *p)
}

Les mêmes règles utilisées pour marshaler des valeurs et des références vers les appels de code non managé sont utilisées lors du marshaling via les interfaces COM. Par exemple, lorsqu'une instance du type valeur Point est passée à partir du .NET Framework vers COM, Point est passé par valeur. Si le type valeur Point est passé par référence, un pointeur vers Point est passé dans la pile. Le marshaleur d'interopérabilité ne prend pas en charge les niveaux supérieurs d'indirection (Point **) dans l'une ou l'autre direction.

Remarque :

Les structures dont l'énumération LayoutKind a la valeur Explicit ne peuvent pas être utilisées dans COM Interop car la bibliothèque de types exportée ne peut pas exprimer une disposition explicite.

Types valeur système

L'espace de noms System possède plusieurs types valeur qui représentent la forme convertie des types primitifs runtime. Par exemple, la structure du type valeur System.Int32 représente la forme convertie de ELEMENT_TYPE_I4. Au lieu de marshaler ces types en tant que structures, comme le sont d'autres types mis en forme, vous les marshalez de la même façon que les types primitifs qu'ils convertissent. System.Int32 est par conséquent marshalé en tant que ELEMENT_TYPE_I4 au lieu de l'être en tant que structure contenant un seul membre de type long. Le tableau suivant dresse une liste des types valeur qui, dans l'espace de noms System, sont des représentations de types primitifs.

Types valeur système

Type d'élément

System.Boolean

ELEMENT_TYPE_BOOLEAN

System.SByte

ELEMENT_TYPE_I1

System.Byte

ELEMENT_TYPE_UI1

System.Char

ELEMENT_TYPE_CHAR

System.Int16

ELEMENT_TYPE_I2

System.UInt16

ELEMENT_TYPE_U2

System.Int32

ELEMENT_TYPE_I4

System.UInt32

ELEMENT_TYPE_U4

System.Int64

ELEMENT_TYPE_I8

System.UInt64

ELEMENT_TYPE_U8

System.Single

ELEMENT_TYPE_R4

System.Double

ELEMENT_TYPE_R8

System.String

ELEMENT_TYPE_STRING

System.IntPtr

ELEMENT_TYPE_I

System.UIntPtr

ELEMENT_TYPE_U

D'autres types valeur dans l'espace de noms System sont gérés différemment. Comme le code non managé possède des formats bien établis pour ces types, le marshaleur a des règles spéciales pour les marshaler. Le tableau suivant répertorie les types valeur spéciaux dans l'espace de noms System, ainsi que le type non managé auquel ils sont marshalés.

Types valeur système

Type IDL

System.DateTime

DATE

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

Le code suivant illustre la définition des types non managés DATE, GUID, DECIMAL, et OLE_COLOR dans la bibliothèque de types Stdole2.

Représentation de la bibliothèque de types

typedef double DATE;
typedef DWORD OLE_COLOR;

typedef struct tagDEC {
    USHORT    wReserved;
    BYTE      scale;
    BYTE      sign;
    ULONG     Hi32;
    ULONGLONG Lo64;
} DECIMAL;

typedef struct tagGUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[ 8 ];
} GUID;

L'exemple de code suivant montre les définitions correspondantes dans l'interface managée IValueTypes.

Public Interface IValueTypes
   Sub M1(d As System.DateTime)
   Sub M2(d As System.Guid)
   Sub M3(d As System.Decimal)
   Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
   void M1(System.DateTime d);
   void M2(System.Guid d);
   void M3(System.Decimal d);
   void M4(System.Drawing.Color d);
}

Représentation de la bibliothèque de types

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};

Voir aussi

Concepts

Types blittables et non blittables

Attributs directionnels

Copie et épinglage

Autres ressources

Comportement de marshaling par défaut