Marshaling par défaut pour les objets

Les paramètres et les champs de type System.Object peuvent être exposés à un code non managé sous la forme de l’un des types suivants :

  • Un variant quand l’objet est un paramètre.

  • Une interface quand l’objet est un champ de structure.

Seul COM interop prend en charge le marshaling des types d’objets. Le comportement par défaut est de marshaler des objets vers des variants COM. Ces règles s’appliquent uniquement au type Object et ne s’appliquent pas aux objets fortement typés issus de la classe Object.

Options de marshaling

Le tableau suivant montre les options de marshaling pour le type de données Object. L’attribut MarshalAsAttribute fournit plusieurs valeurs d’énumération UnmanagedType pour marshaler des objets.

Type d'énumération Description du format non managé
UnmanagedType.Struct

(valeur par défaut des paramètres)
Variant de style COM.
UnmanagedType.Interface Interface IDispatch, si possible ; sinon, interface IUnknown.
UnmanagedType.IUnknown

(valeur par défaut des champs)
Interface IUnknown.
UnmanagedType.IDispatch Interface IDispatch.

L’exemple suivant montre la définition d’interface managée de MarshalObject.

Interface MarshalObject
   Sub SetVariant(o As Object)
   Sub SetVariantRef(ByRef o As Object)
   Function GetVariant() As Object

   Sub SetIDispatch( <MarshalAs(UnmanagedType.IDispatch)> o As Object)
   Sub SetIDispatchRef(ByRef <MarshalAs(UnmanagedType.IDispatch)> o _
      As Object)
   Function GetIDispatch() As <MarshalAs(UnmanagedType.IDispatch)> Object
   Sub SetIUnknown( <MarshalAs(UnmanagedType.IUnknown)> o As Object)
   Sub SetIUnknownRef(ByRef <MarshalAs(UnmanagedType.IUnknown)> o _
      As Object)
   Function GetIUnknown() As <MarshalAs(UnmanagedType.IUnknown)> Object
End Interface
interface MarshalObject {
   void SetVariant(Object o);
   void SetVariantRef(ref Object o);
   Object GetVariant();

   void SetIDispatch ([MarshalAs(UnmanagedType.IDispatch)]Object o);
   void SetIDispatchRef([MarshalAs(UnmanagedType.IDispatch)]ref Object o);
   [MarshalAs(UnmanagedType.IDispatch)] Object GetIDispatch();
   void SetIUnknown ([MarshalAs(UnmanagedType.IUnknown)]Object o);
   void SetIUnknownRef([MarshalAs(UnmanagedType.IUnknown)]ref Object o);
   [MarshalAs(UnmanagedType.IUnknown)] Object GetIUnknown();
}

Le code suivant exporte l’interface MarshalObject vers une bibliothèque de types.

interface MarshalObject {
   HRESULT SetVariant([in] VARIANT o);
   HRESULT SetVariantRef([in,out] VARIANT *o);
   HRESULT GetVariant([out,retval] VARIANT *o)
   HRESULT SetIDispatch([in] IDispatch *o);
   HRESULT SetIDispatchRef([in,out] IDispatch **o);
   HRESULT GetIDispatch([out,retval] IDispatch **o)
   HRESULT SetIUnknown([in] IUnknown *o);
   HRESULT SetIUnknownRef([in,out] IUnknown **o);
   HRESULT GetIUnknown([out,retval] IUnknown **o)
}

Notes

Le marshaleur d’interopérabilité libère automatiquement tout objet alloué à l’intérieur du variant après l’appel.

L’exemple suivant illustre un type valeur mis en forme.

Public Structure ObjectHolder
   Dim o1 As Object
   <MarshalAs(UnmanagedType.IDispatch)> Public o2 As Object
End Structure
public struct ObjectHolder {
   Object o1;
   [MarshalAs(UnmanagedType.IDispatch)]public Object o2;
}

Le code suivant exporte le type mis en forme vers une bibliothèque de types.

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

Marshaling d’un objet vers une interface

Quand un objet est exposé à COM en tant qu’interface, cette interface est l’interface de classe pour le type managé Object (l’interface _Object). Cette interface est de type IDispatch (UnmanagedType) ou IUnknown (UnmanagedType.IUnknown) dans la bibliothèque de types résultante. Les clients COM peuvent appeler dynamiquement les membres de la classe managée ou tout membre implémenté par ses classes dérivées via l’interface _Object. Le client peut également appeler QueryInterface pour obtenir toute autre interface explicitement implémentée par le type managé.

Marshaling d’un objet vers un variant

Lorsqu’un objet est marshalé en variant, le type variant interne est déterminé au moment de l’exécution en fonction des règles suivantes :

  • Si la référence d’objet est null (Nothing en Visual Basic), l’objet est marshalé en variant de type VT_EMPTY.

  • Si l’objet est une instance d’un type énuméré dans le tableau suivant, le type variant résultant est déterminé par les règles du marshaleur et illustré dans le tableau.

  • Les autres objets qui nécessitent de contrôler explicitement le comportement de marshaling peuvent implémenter l’interface IConvertible. Dans ce cas, le type variant est déterminé par le code de type retourné à partir de la méthode IConvertible.GetTypeCode. Sinon, l’objet est marshalé en tant que variant de type VT_UNKNOWN.

Marshaling de types système vers des variants

Le tableau suivant montre les types d’objets managés et leurs types variant COM correspondants. Ces types sont convertis uniquement quand la signature de la méthode appelée est de type System.Object.

Type d’objet Type variant COM
Référence d’objet null (Nothing en Visual Basic). VT_EMPTY
System.DBNull VT_NULL
System.Runtime.InteropServices.ErrorWrapper VT_ERROR
System.Reflection.Missing VT_ERROR avec E_PARAMNOTFOUND
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.CurrencyWrapper VT_CY
System.Boolean VT_BOOL
System.SByte VT_I1
System.Byte VT_UI1
System.Int16 VT_I2
System.UInt16 VT_UI2
System.Int32 VT_I4
System.UInt32 VT_UI4
System.Int64 VT_I8
System.UInt64 VT_UI8
System.Single VT_R4
System.Double VT_R8
System.Decimal VT_DECIMAL
System.DateTime VT_DATE
System.String VT_BSTR
System.IntPtr VT_INT
System.UIntPtr VT_UINT
System.Array VT_ARRAY

En utilisant l’interface MarshalObject définie dans l’exemple précédent, l’exemple de code suivant démontre comment passer plusieurs types de variants à un serveur COM.

Dim mo As New MarshalObject()
mo.SetVariant(Nothing)         ' Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value) ' Marshal as variant of type VT_NULL.
mo.SetVariant(CInt(27))        ' Marshal as variant of type VT_I2.
mo.SetVariant(CLng(27))        ' Marshal as variant of type VT_I4.
mo.SetVariant(CSng(27.0))      ' Marshal as variant of type VT_R4.
mo.SetVariant(CDbl(27.0))      ' Marshal as variant of type VT_R8.
MarshalObject mo = new MarshalObject();
mo.SetVariant(null);            // Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value); // Marshal as variant of type VT_NULL.
mo.SetVariant((int)27);          // Marshal as variant of type VT_I2.
mo.SetVariant((long)27);          // Marshal as variant of type VT_I4.
mo.SetVariant((single)27.0);   // Marshal as variant of type VT_R4.
mo.SetVariant((double)27.0);   // Marshal as variant of type VT_R8.

Les types COM qui n’ont pas de types managés correspondants peuvent être marshalés à l’aide des classes wrapper comme ErrorWrapper, DispatchWrapper, UnknownWrapper et CurrencyWrapper. L’exemple de code suivant démontre comment utiliser ces wrappers pour passer différents types de variants à un serveur COM.

Imports System.Runtime.InteropServices
' Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(New UnknownWrapper(inew))
' Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(New DispatchWrapper(inew))
' Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(New ErrorWrapper(&H80054002))
' Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(New CurrencyWrapper(New Decimal(5.25)))
using System.Runtime.InteropServices;
// Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(new UnknownWrapper(inew));
// Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(new DispatchWrapper(inew));
// Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(new ErrorWrapper(0x80054002));
// Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(new CurrencyWrapper(new Decimal(5.25)));

Les classes wrapper sont définies dans l’espace de noms System.Runtime.InteropServices.

Marshaling de l’interface IConvertible vers un variant

D’autres types que ceux répertoriés dans la section précédente peuvent contrôler la manière dont ils sont marshalés en implémentant l’interface IConvertible. Si l’objet implémente l’interface IConvertible, le type variant COM est déterminé au moment de l’exécution par la valeur de l’énumération TypeCode retournée à partir de la méthode IConvertible.GetTypeCode.

Le tableau suivant montre les valeurs possibles pour l’énumération TypeCode et le type variant COM correspondant pour chaque valeur.

TypeCode Type variant COM
TypeCode.Empty VT_EMPTY
TypeCode.Object VT_UNKNOWN
TypeCode.DBNull VT_NULL
TypeCode.Boolean VT_BOOL
TypeCode.Char VT_UI2
TypeCode.Sbyte VT_I1
TypeCode.Byte VT_UI1
TypeCode.Int16 VT_I2
TypeCode.UInt16 VT_UI2
TypeCode.Int32 VT_I4
TypeCode.UInt32 VT_UI4
TypeCode.Int64 VT_I8
TypeCode.UInt64 VT_UI8
TypeCode.Single VT_R4
TypeCode.Double VT_R8
TypeCode.Decimal VT_DECIMAL
TypeCode.DateTime VT_DATE
TypeCode.String VT_BSTR
Non pris en charge. VT_INT
Non pris en charge. VT_UINT
Non pris en charge. VT_ARRAY
Non pris en charge. VT_RECORD
Non pris en charge. VT_CY
Non pris en charge. VT_VARIANT

La valeur du variant COM est déterminée en appelant l’interface IConvertible.ToType, où ToType est la routine de conversion qui correspond au type retourné par IConvertible.GetTypeCode. Par exemple, un objet qui retourne TypeCode.Double depuis IConvertible.GetTypeCode est marshalé en tant que variant COM de type VT_R8. Vous pouvez obtenir la valeur du variant (stockée dans le champ dblVal du variant COM) en effectuant un cast en une interface IConvertible et en appelant la méthode ToDouble.

Marshaling d’un variant vers un objet

Lors du marshaling d’un variant vers un objet, le type, et parfois la valeur, du variant marshalé détermine le type d’objet produit. Le tableau suivant identifie chaque type variant et le type d’objet correspondant que le marshaleur crée quand un variant est transféré de COM à .NET Framework.

Type variant COM Type d’objet
VT_EMPTY Référence d’objet null (Nothing en Visual Basic).
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject ou null si (pdispVal == null)
VT_UNKNOWN System.__ComObject ou null si (punkVal == null)
VT_ERROR System.UInt32
VT_BOOL System.Boolean
VT_I1 System.SByte
VT_UI1 System.Byte
VT_I2 System.Int16
VT_UI2 System.UInt16
VT_I4 System.Int32
VT_UI4 System.UInt32
VT_I8 System.Int64
VT_UI8 System.UInt64
VT_R4 System.Single
VT_R8 System.Double
VT_DECIMAL System.Decimal
VT_DATE System.DateTime
VT_BSTR System.String
VT_INT System.Int32
VT_UINT System.UInt32
VT_ARRAY | VT_* System.Array
VT_CY System.Decimal
VT_RECORD Type valeur boxed correspondant.
VT_VARIANT Non pris en charge.

Les types variant passés à partir de COM vers le code managé, puis repassés à COM peuvent ne pas conserver le même type variant durant la durée de l’appel. Envisagez ce qui se passe quand un variant de type VT_DISPATCH est passé à partir de COM vers le .NET Framework. Lors du marshaling, le variant est converti en System.Object. Si l’objet est ensuite repassé à COM, il est remarshalé vers un variant de type VT_UNKNOWN. Rien ne garantit que le variant produit quand un objet est marshalé à partir de code managé vers COM sera du même type que le variant utilisé à l’origine pour produire l’objet.

Marshaling des variants ByRef

Bien que les variants puissent eux-mêmes être passés par valeur ou par référence, l’indicateur VT_BYREF peut être également utilisé avec n’importe quel type de variant pour indiquer que le contenu du variant est passé par référence plutôt que par valeur. La différence entre le marshaling de variants par référence et le marshaling d’un variant dont l’indicateur VT_BYREF est défini peut prêter à confusion. L’illustration suivante clarifie ces différences :

Diagram that shows variant passed on the stack. Variants passés par valeur et par référence

Comportement par défaut de marshaling d’objets et de variants par valeur

  • Lors du passage d’objets à partir de code managé vers COM, le contenu de l’objet est copié dans un nouveau variant créé par le marshaleur à l’aide des règles définies dans Marshaling d’un objet vers un variant. Les changements apportés au variant côté non managé ne sont pas retournés vers l’objet d’origine au retour de l’appel.

  • Lors du passage de variants à partir de COM vers du code managé, le contenu du variant est copié dans un objet nouvellement créé à l’aide des règles définies dans Marshaling d’un variant vers un objet. Les changements apportés à l’objet côté managé ne sont pas retournés vers le variant d’origine au retour de l’appel.

Comportement par défaut de marshaling d’objets et de variants par valeur

Pour retourner des changements à l’appelant, les paramètres doivent être passés par référence. Par exemple, vous pouvez utiliser le mot clé ref en C# (ou ByRef en code managé Visual Basic) pour passer des paramètres par référence. Dans COM, les paramètres de référence sont passés à l’aide d’un pointeur tel que variant *.

  • Lors du passage d’un objet à COM par référence, le marshaleur crée un variant et copie le contenu de la référence d’objet dans le variant avant que l’appel ne soit effectué. Le variant est passé à la fonction non managée où l’utilisateur est libre de changer le contenu du variant. Au retour de l’appel, tout changement apporté au variant côté non managé est retourné vers l’objet d’origine. Si le type de variant est différent du type du variant passé à l’appel, les changements sont retournés à un objet d’un type différent. Cela signifie que le type de l’objet passé dans l’appel peut être différent du type de l’objet retourné à partir de l’appel.

  • Lors du passage d’un variant au code managé par référence, le marshaleur crée un objet et copie le contenu du variant dans l’objet avant que l’appel ne soit effectué. Une référence à l’objet est passée à la fonction managée, où l’utilisateur est libre de changer l’objet. Au retour de l’appel, tout changement apporté à l’objet référencé est retourné vers le variant d’origine. Si le type de l’objet est différent du type de l’objet passé dans l’appel, le type du variant d’origine est changé et la valeur est retournée dans le variant. Là encore, le type du variant passé dans l’appel peut être différent du type du variant retourné à partir de l’appel.

Comportement par défaut de marshaling d’un variant dont l’indicateur VT_BYREF est défini

  • Un variant passé à du code managé par valeur peut avoir l’indicateur VT_BYREF défini pour indiquer que le variant contient une référence plutôt qu’une valeur. Dans ce cas, le variant est toujours marshalé vers un objet, car le variant est passé par valeur. Le marshaleur déréférence automatiquement le contenu du variant et le copie dans un objet nouvellement créé avant d’effectuer l’appel. L’objet est ensuite passé à la fonction managée ; cependant, au retour de l’appel, l’objet n’est pas retourné dans le variant d’origine. Les changements apportés à l’objet managé sont perdus.

    Attention

    Il est impossible de changer la valeur d’un variant passé par valeur, même si l’indicateur VT_BYREF de ce variant est défini.

  • Un variant passé à du code managé par référence peut aussi avoir l’indicateur VT_BYREF défini pour indiquer que le variant contient une autre référence. Si c’est le cas, le variant est marshalé vers un objet ref parce qu’il est passé par référence. Le marshaleur déréférence automatiquement le contenu du variant et le copie dans un objet nouvellement créé avant d’effectuer l’appel. Au retour de l’appel, la valeur de l’objet est retournée vers la référence située dans le variant d’origine uniquement si l’objet est du même type que l’objet passé. Autrement dit, la propagation ne change pas le type d’un variant avec l’indicateur VT_BYREF défini. Si le type de l’objet est modifié durant l’appel, une exception InvalidCastException se produit au retour de l’appel.

Le tableau suivant résume les règles de propagation pour les variants et les objets.

Du À Changements retournés
Variantv Objecto Jamais
Objecto Variantv Jamais
Variant*pv Ref Objecto Toujours
Ref objecto Variant*pv Toujours
Variantv(VT_BYREF|VT_*) Objecto Jamais
Variantv(VT_BYREF|VT_) Ref Objecto Uniquement si le type n’a pas changé.

Voir aussi