Partager via


Comportement de marshalling par défaut

Le marshaling d’interopérabilité fonctionne sur des règles qui déterminent la façon dont les données associées aux paramètres de méthode se comportent à mesure qu’elles passent entre la mémoire managée et non managée. Ces règles intégrées contrôlent ces activités de marshaling en tant que transformations de type de données, si un appelé peut modifier les données transmises à celui-ci et retourner ces modifications à l’appelant, et dans quelles circonstances le marshaller fournit des optimisations de performances.

Cette section identifie les caractéristiques comportementales par défaut du service de marshaling d’interopérabilité. Il présente des informations détaillées sur les tableaux de marshaling, les types booléens, les types char, les délégués, les classes, les objets, les chaînes et les structures.

Remarque

Le marshaling de types génériques n’est pas pris en charge. Pour plus d’informations, consultez Interopération à l’aide de types génériques.

Gestion de la mémoire avec le marshaller d’interopérabilité

Le marshaller d’interopérabilité tente toujours de libérer de la mémoire allouée par du code non managé. Ce comportement est conforme aux règles de gestion de la mémoire COM, mais diffère des règles qui régissent le langage C++natif.

La confusion peut survenir si vous prévoyez un comportement C++ natif (sans libération de mémoire) lors de l’utilisation d’un appel de plateforme, ce qui libère automatiquement la mémoire pour les pointeurs. Par exemple, l’appel de la méthode non managée suivante à partir d’une DLL C++ ne libère pas automatiquement de mémoire.

Signature non managée

BSTR MethodOne (BSTR b) {  
     return b;  
}  

Toutefois, si vous définissez la méthode en tant que prototype d’appel de plateforme, remplacez chaque type BSTR par un String type et appelez MethodOne, le Common Language Runtime tente de libérer b deux fois. Vous pouvez modifier le comportement de marshaling à l’aide IntPtr de types plutôt que de types String .

Le runtime utilise toujours la méthode CoTaskMemFree sur Windows et la méthode gratuite sur d’autres plateformes pour libérer de la mémoire. Si la mémoire avec laquelle vous travaillez n’a pas été allouée avec la méthode CoTaskMemAlloc sur Windows ou malloc sur d’autres plateformes, vous devez utiliser un IntPtr et libérer la mémoire manuellement à l’aide de la méthode appropriée. De même, vous pouvez éviter la libération automatique de la mémoire dans les situations où la mémoire ne doit jamais être libérée, par exemple lors de l’utilisation de la fonction GetCommandLine de Kernel32.dll, qui retourne un pointeur vers la mémoire du noyau. Pour plus d’informations sur la libération manuelle de la mémoire, consultez l’exemple De mémoires tampons.

Marshaling par défaut pour les classes

Les classes peuvent être marshalées uniquement par l’interopérabilité COM et sont toujours marshalées en tant qu’interfaces. Dans certains cas, l’interface utilisée pour marshaler la classe est appelée interface de classe. Pour plus d’informations sur la substitution de l’interface de classe avec une interface de votre choix, consultez Présentation de l’interface de classe.

Passage de classes à COM

Lorsqu’une classe managée est passée à COM, le marshaller d’interopérabilité encapsule automatiquement la classe avec un proxy COM et transmet l’interface de classe produite par le proxy à l’appel de méthode COM. Le proxy délègue ensuite tous les appels sur l’interface de classe à l’objet managé. Le proxy expose également d’autres interfaces qui ne sont pas explicitement implémentées par la classe. Le proxy implémente automatiquement des interfaces telles que IUnknown et IDispatch pour le compte de la classe.

Passage de classes à .NET Code

Les coclasses ne sont généralement pas utilisées comme arguments de méthode dans COM. Au lieu de cela, une interface par défaut est généralement passée à la place de la coclasse.

Lorsqu’une interface est passée dans du code managé, l’interopérabilité marshaller est responsable de l’habillage de l’interface avec le wrapper approprié et de passer le wrapper à la méthode managée. Déterminer le wrapper à utiliser peut être difficile. Chaque instance d’un objet COM a un wrapper unique unique, quel que soit le nombre d’interfaces implémentées par l’objet. Par exemple, un seul objet COM qui implémente cinq interfaces distinctes n’a qu’un seul wrapper. Le même wrapper expose les cinq interfaces. Si deux instances de l’objet COM sont créées, deux instances du wrapper sont créées.

Pour que le wrapper conserve le même type tout au long de sa durée de vie, le marshalleur d’interopérabilité doit identifier le wrapper correct la première fois qu’une interface exposée par l’objet est passée à travers le marshaller. Le marshaller identifie l’objet en examinant l’une des interfaces que l’objet implémente.

Par exemple, le marshaller détermine que le wrapper de classe doit être utilisé pour encapsuler l’interface passée dans le code managé. Lorsque l’interface est passée d’abord par le marshaller, le marshaller vérifie si l’interface provient d’un objet connu. Cette vérification se produit dans deux situations :

  • Une interface est implémentée par un autre objet managé passé à COM ailleurs. Le marshaller peut facilement identifier les interfaces exposées par des objets managés et est en mesure de faire correspondre l’interface avec l’objet managé qui fournit l’implémentation. L’objet managé est ensuite passé à la méthode et aucun wrapper n’est nécessaire.

  • Un objet qui a déjà été encapsulé implémente l’interface. Pour déterminer si c’est le cas, le marshaller interroge l’objet pour son interface IUnknown et compare l’interface retournée aux interfaces d’autres objets déjà encapsulés. Si l’interface est identique à celle d’un autre wrapper, les objets ont la même identité et le wrapper existant est passé à la méthode.

Si une interface ne provient pas d’un objet connu, le marshaller effectue les opérations suivantes :

  1. Le marshaller interroge l’objet pour l’interface IProvideClassInfo2 . S’il est fourni, le marshaller utilise le CLSID retourné par IProvideClassInfo2.GetGUID pour identifier la coclasse fournissant l’interface. Avec le CLSID, le marshaller peut localiser le wrapper à partir du Registre si l’assembly a déjà été inscrit.

  2. Le marshaller interroge l’interface pour l’interface IProvideClassInfo . S’il est fourni, le marshaller utilise iTypeInfo retourné par IProvideClassInfo.GetClassinfo pour déterminer le CLSID de la classe exposant l’interface. Le marshaller peut utiliser le CLSID pour localiser les métadonnées du wrapper.

  3. Si le marshaller ne peut toujours pas identifier la classe, il encapsule l’interface avec une classe wrapper générique appelée System.__ComObject.

Marshaling par défaut pour les délégués

Un délégué managé est marshalé en tant qu’interface COM ou en tant que pointeur de fonction, en fonction du mécanisme appelant :

  • Pour l’appel de plateforme, un délégué est marshalé comme pointeur de fonction non managé par défaut.

  • Pour l’interopérabilité COM, un délégué est marshalé en tant qu’interface COM de type _Delegate par défaut. L’interface _Delegate est définie dans la bibliothèque de types Mscorlib.tlb et contient la Delegate.DynamicInvoke méthode, qui vous permet d’appeler la méthode référencée par le délégué.

Le tableau suivant présente les options de marshaling pour le type de données délégué managé. L’attribut MarshalAsAttribute fournit plusieurs UnmanagedType valeurs d’énumération pour marshaler les délégués.

Type d’énumération Description du format non managé
UnmanagedType.FunctionPtr Pointeur de fonction non managé.
UnmanagedType.Interface Interface de type _Delegate, telle que définie dans Mscorlib.tlb.

Considérez l’exemple de code suivant dans lequel les méthodes d’exportation DelegateTestInterface sont exportées vers une bibliothèque de types COM. Notez que seuls les délégués marqués avec le mot clé ref (ou ByRef) sont passés en tant que paramètres In/Out.

using System;  
using System.Runtime.InteropServices;  
  
public interface DelegateTest {  
void m1(Delegate d);  
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}  

Représentation de bibliothèque de types

importlib("mscorlib.tlb");  
interface DelegateTest : IDispatch {  
[id(…)] HRESULT m1([in] _Delegate* d);  
[id(…)] HRESULT m2([in] _Delegate* d);  
[id(…)] HRESULT m3([in, out] _Delegate** d);  
[id()] HRESULT m4([in] int d);  
[id()] HRESULT m5([in, out] int *d);  
   };  

Un pointeur de fonction peut être déréférencement, tout comme n’importe quel autre pointeur de fonction non managé peut être déréférencement.

Dans cet exemple, lorsque les deux délégués sont marshalés comme UnmanagedType.FunctionPtr, le résultat est un int pointeur vers un int. Étant donné que les types délégués sont en cours de marshaled, int ici représente un pointeur vers un vide (void*), qui est l’adresse du délégué en mémoire. En d’autres termes, ce résultat est spécifique aux systèmes Windows 32 bits, car int ici représente la taille du pointeur de fonction.

Remarque

Une référence au pointeur de fonction vers un délégué managé détenu par du code non managé n’empêche pas le Common Language Runtime d’effectuer un garbage collection sur l’objet managé.

Par exemple, le code suivant est incorrect, car la référence à l’objet cb , passée à la SetChangeHandler méthode, ne reste cb pas vivante au-delà de la durée de vie de la Test méthode. Une fois que l’objet est récupéré par la cb mémoire, le pointeur de fonction passé à SetChangeHandler n’est plus valide.

public class ExternalAPI {  
   [DllImport("External.dll")]  
   public static extern void SetChangeHandler(  
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);  
}  
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);  
public class CallBackClass {  
   public bool OnChange(string S){ return true;}  
}  
internal class DelegateTest {  
   public static void Test() {  
      CallBackClass cb = new CallBackClass();  
      // Caution: The following reference on the cb object does not keep the
      // object from being garbage collected after the Main method
      // executes.  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }  
}  

Pour compenser le garbage collection inattendu, l’appelant doit s’assurer que l’objet cb est conservé actif tant que le pointeur de fonction non managé est en cours d’utilisation. Si vous le souhaitez, vous pouvez avoir le code non managé notifier le code managé lorsque le pointeur de fonction n’est plus nécessaire, comme l’illustre l’exemple suivant.

internal class DelegateTest {  
   CallBackClass cb;  
   // Called before ever using the callback function.  
   public static void SetChangeHandler() {  
      cb = new CallBackClass();  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));  
   }  
   // Called after using the callback function for the last time.  
   public static void RemoveChangeHandler() {  
      // The cb object can be collected now. The unmanaged code is
      // finished with the callback function.  
      cb = null;  
   }  
}  

Marshaling par défaut pour les types valeur

La plupart des types valeur, tels que les entiers et les nombres à virgule flottante, sont blittables et ne nécessitent pas de marshaling. D’autres types non blittables ont des représentations dissimilantes dans la mémoire managée et non managée et nécessitent un marshaling. D’autres types nécessitent une mise en forme explicite sur la limite d’interopérabilité.

Cette section fournit des informations sur les types de valeurs mis en forme suivants :

Outre la description des types mis en forme, cette rubrique identifie les types valeur système qui ont un comportement de marshaling 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 être l’une des valeurs d’énumération suivantes LayoutKind :

  • LayoutKind.Auto

    Indique que le Common Language Runtime est libre de réorganiser les membres du type pour une efficacité. Toutefois, lorsqu’un type valeur est passé à du code non managé, la disposition des membres est prévisible. Une tentative de marshaler une telle structure provoque automatiquement une exception.

  • LayoutKind.Sequential

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

  • LayoutKind.Explicit

    Indique que les membres sont disposés en fonction du FieldOffsetAttribute champ fourni.

Types valeur utilisés dans l’appel de plateforme

Dans l’exemple suivant, les PointRect types fournissent des informations de disposition de membre à 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 en code non managé, ces types mis en forme sont marshalés en tant que structures de style C. Cela permet d’appeler facilement une API non managée qui a des arguments de structure. Par exemple, les POINT structures RECT peuvent être transmises à la fonction PtInRect de l’API Microsoft Windows comme suit :

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

Vous pouvez passer des structures à l’aide de la définition d’appel de plateforme suivante :

Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "User32.dll" (
        ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
   [DllImport("User32.dll")]
   internal static extern bool PtInRect(ref Rect r, Point p);
}

Le Rect type de valeur doit être passé par référence, car l’API non managée s’attend à ce qu’un pointeur vers une RECT fonction soit transmis à la fonction. Le Point type de valeur est passé par valeur, car l’API non managée s’attend POINT à ce qu’elle soit transmise sur 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é sur la pile.

Remarque

Lorsqu’un type mis en forme est marshalé en tant que structure, seuls les champs du type sont accessibles. Si le type a des méthodes, des propriétés ou des événements, ils sont inaccessibles à partir du code non managé.

Les classes peuvent également être marshalées en code non managé en tant que structures de style C, à condition qu’elles aient une disposition membre fixe. Les informations de disposition de membre pour une classe sont également fournies avec l’attribut StructLayoutAttribute . La principale différence entre les types valeur avec la disposition fixe et les classes avec une disposition fixe est la façon dont ils sont marshalés en code non managé. Les types valeur sont transmis par valeur (sur la pile) et, par conséquent, les modifications apportées aux membres du type par l’appelé ne sont pas visibles par l’appelant. Les types de référence sont passés par référence (une référence au type est transmise sur la pile) ; par conséquent, toutes les modifications apportées aux membres de type blittable d’un type par l’appelé sont vues par l’appelant.

Remarque

Si un type référence a des membres de types non blittables, la conversion est requise deux fois : la première fois qu’un argument est passé au côté non managé et la deuxième fois à partir de l’appel. En raison de cette surcharge ajoutée, les paramètres In/Out doivent être appliqués explicitement à un argument si l’appelant souhaite voir les modifications apportées par l’appelé.

Dans l’exemple suivant, la SystemTime classe a une disposition de membre séquentielle et peut être passée à la fonction GetSystemTime de l’API Windows.

<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 plateforme équivalente pour GetSystemTime est la suivante :

Friend Class NativeMethods
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
   [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
   internal static extern void GetSystemTime(SystemTime st);
}

Notez que l’argument SystemTime n’est pas typé comme argument de référence, car SystemTime il s’agit d’une classe, et non d’un type valeur. Contrairement aux types valeur, les classes sont toujours passées par référence.

L’exemple de code suivant montre une classe différente Point qui a une méthode appelée SetXY. Étant donné que le type a une disposition séquentielle, il peut être passé à du code non managé et marshalé en tant que structure. Toutefois, le SetXY membre n’est pas appelant à 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 également être passés aux appels de méthode COM Interop. En fait, lorsqu’ils sont exportés vers une bibliothèque de types, les types valeur sont automatiquement convertis en structures. Comme l’illustre l’exemple suivant, le Point type valeur devient une définition de type (typedef) avec le nom Point. Toutes les références au Point type valeur ailleurs dans la bibliothèque de types sont remplacées par le Point typedef.

Représentation de 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 les valeurs et les références aux appels d’appel de plateforme sont utilisées lors du marshaling via des interfaces COM. Par exemple, lorsqu’une instance du Point type valeur est passée du .NET Framework à COM, elle Point est passée par valeur. Si le Point type de valeur est passé par référence, un pointeur vers une Point pile est transmis. Le marshalleur d’interopérabilité ne prend pas en charge les niveaux plus élevés d’indirection (point **) dans les deux sens.

Remarque

Les structures ayant la LayoutKind valeur d’énumération définie sur Explicit ne peuvent pas être utilisées dans l’interopérabilité COM, car la bibliothèque de types exportée ne peut pas exprimer une disposition explicite.

Types de valeurs système

L’espace System de noms a plusieurs types valeur qui représentent la forme boxed des types primitifs runtime. Par exemple, la structure de type System.Int32 valeur représente la forme boxée de ELEMENT_TYPE_I4. Au lieu de marshaler ces types en tant que structures, comme d’autres types mis en forme sont, vous les marshalez de la même façon que les types primitifs qu’ils boxent. System.Int32 est donc marshalé comme ELEMENT_TYPE_I4 au lieu d’une structure contenant un seul membre de type long. Le tableau suivant contient une liste des types valeur dans l’espace de noms System qui sont des représentations boxed des types primitifs.

Type de 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

Certains autres types de valeurs dans l’espace de noms System sont gérés différemment. Étant donné que le code non managé a déjà des formats bien établis pour ces types, le marshalleur a des règles spéciales pour les marshaling. Le tableau suivant répertorie les types de valeurs spéciales dans l’espace de noms System , ainsi que le type non managé auquel ils sont marshalés.

Type de valeur système Type IDL
System.DateTime DATE
System.Decimal DÉCIMAL
System.Guid GUID
System.Drawing.Color OLE_COLOR

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

Représentation de 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;  

Le 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 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