Marshaling par défaut pour les objets

Mise à jour : novembre 2007

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

  • Un variant lorsque l'objet est un paramètre.

  • Une interface lorsque l'objet est un champ de structure.

Seul COM interop prend en charge le marshaling des types d'objet. 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.

Cette rubrique fournit les informations supplémentaires suivantes sur le marshaling des types d'objets :

  • Options de marshaling

  • Marshaling d'un objet vers une interface

  • Marshaling d'un objet vers un variant

  • Marshaling de variant vers l'objet

  • Marshaling des variants ByRef

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 énumération

Description de 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) 
}
Remarque :

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

Lorsqu'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.IDispatch) 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é vers un 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é à un 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 lorsque 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

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 telles que 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.Sbyte

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 par appel à l'interface IConvertible.ToTypeToType est la routine de conversion qui correspond au type retourné par IConvertible.GetTypeCode. Par exemple, un objet qui retourne TypeCode.Double de IConvertible.GetTypeCode est marshalé comme 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 de variant vers l'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 lorsqu'un variant est passé à partir de COM vers le .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 lorsqu'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 Object est ensuite repassé à COM, il est remarshalé vers un variant de type VT_UNKNOWN. Rien ne garantit que le variant produit lorsqu'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.

Variants passés par valeur et par référence

Variant passé sur la pile

Comportement par défaut pour le 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 de variant vers l'objet. Les changements apportés au variant côté managé ne sont pas retournés vers le variant d'origine au retour de l'appel.

Comportement par défaut pour le marshaling d'objets et de variants par référence

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 nouveau 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 nouvel 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 vers 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. De même, 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 pour le marshaling d'un variant où l'indicateur VT_BYREF a été 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 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é à 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 à la référence à l'intérieur du variant d'origine uniquement si l'objet est du même type que l'objet passé. C'est-à-dire que le retour ne change pas le type d'un variant dont l'indicateur VT_BYREF est 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.

De

Vers

Changements retournés

Variant v

Objet o

Jamais

Objet o

Variant v

Jamais

Variant *pv

Objet Ref o

Toujours

Objet Ref o

Variant *pv

Toujours

Variant v(VT_BYREF|VT_*)

Objet o

Jamais

Variant v(VT_BYREF|VT_)

Objet Ref o

Uniquement si le type n'a pas changé.

Voir aussi

Concepts

Types blittables et non blittables

Attributs directionnels

Copie et épinglage

Autres ressources

Comportement de marshaling par défaut