Marshalling predefinito per gli oggetti

I parametri e i campi tipizzati come System.Object possono essere esposti al codice non gestito come uno dei tipi seguenti:

  • Una variante quando l'oggetto è un parametro.

  • Un'interfaccia quando l'oggetto è un campo della struttura.

Solo l'interoperabilità COM supporta il marshalling per i tipi di oggetto. Il comportamento predefinito è il marshalling degli oggetti alle varianti COM. Queste regole si applicano solo al tipo Object e non agli oggetti fortemente tipizzati derivanti dalla classe Object.

Opzioni di marshalling

La tabella seguente illustra le opzioni di marshalling per il tipo di dati Object. L'attributo MarshalAsAttribute fornisce alcuni valori di enumerazione UnmanagedType per effettuare il marshalling di oggetti.

Tipo di enumerazione Descrizione del formato non gestito
UnmanagedType.Struct

(predefinita per i parametri)
Una variante di tipo COM.
UnmanagedType.Interface Un'interfaccia IDispatch, se possibile; in caso contrario, un'interfaccia IUnknown.
UnmanagedType.IUnknown

(predefinita per i campi)
Un' interfaccia IUnknown.
UnmanagedType.IDispatch Un'interfaccia IDispatch.

L'esempio seguente illustra la definizione dell'interfaccia gestita per 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();
}

Il codice seguente esporta l'interfaccia MarshalObject in una libreria dei tipi.

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)
}

Nota

Il gestore di marshalling di interoperabilità libera automaticamente gli oggetti allocati nella variante dopo la chiamata.

L'esempio seguente illustra un tipo valore formattato.

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;
}

Il codice seguente esporta il tipo formattato in una libreria dei tipi.

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

Marshalling da oggetto a interfaccia

Quando un oggetto viene esposto a COM come interfaccia, tale interfaccia è l'interfaccia di classe del tipo gestito Object (interfaccia _Object). Questa interfaccia viene tipizzata come IDispatch (UnmanagedType) o IUnknown (UnmanagedType.IUnknown) nella libreria dei tipi risultante. I client COM possono richiamare in modo dinamico i membri della classe gestita o eventuali membri implementati dalle classi derivate tramite l'interfaccia _Object. Il client può anche chiamare QueryInterface per ottenere le altre interfacce implementate in modo esplicito dal tipo gestito.

Marshalling da oggetto a variante

Quando viene effettuato il marshalling di un oggetto a una variante, il tipo di variante interno viene determinato in fase di esecuzione, in base alle regole seguenti:

  • Se il riferimento all'oggetto è null (Nothing in Visual Basic), viene effettuato il marshalling dell'oggetto a una variante di tipo VT_EMPTY.

  • Se l'oggetto è un'istanza di un tipo elencato nella tabella seguente, il tipo di variante risultante viene determinato dalle regole incluse nel gestore di marshalling e visualizzate nella tabella.

  • Gli altri oggetti che devono controllare in modo esplicito il comportamento di marshalling possono implementare l'interfaccia IConvertible. In tal caso, il tipo di variante viene determinato dal codice del tipo restituito dal metodo IConvertible.GetTypeCode. In caso contrario, viene effettuato il marshalling dell'oggetto come variante di tipo VT_UNKNOWN.

Marshalling di tipi di sistema a variante

La tabella seguente illustra i tipi di oggetto gestito e i corrispondenti tipi di varianti COM. Questi tipi vengono convertiti solo quando la firma del metodo chiamato è di tipo System.Object.

Object type Tipo di variante COM
Riferimento all'oggetto null (Nothing in Visual Basic). VT_EMPTY
System.DBNull VT_NULL
System.Runtime.InteropServices.ErrorWrapper VT_ERROR
System.Reflection.Missing VT_ERROR con 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

Usando l'interfaccia MarshalObject definita nell'esempio precedente, l'esempio di codice seguente illustra come passare diversi tipi di varianti a un server 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.

È possibile effettuare il marshalling dei tipi COM privi di tipi gestiti corrispondenti usando classi wrapper, ad esempio ErrorWrapper, DispatchWrapper, UnknownWrapper e CurrencyWrapper. L'esempio di codice seguente illustra come usare questi wrapper per passare diversi tipi di varianti a un server 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)));

Le classi wrapper vengono definite nello spazio dei nomi System.Runtime.InteropServices.

Marshalling dell'interfaccia IConvertible a variante

I tipi diversi da quelli elencati nella sezione precedente possono controllare come viene effettuato il marshalling implementando l'interfaccia IConvertible. Se l'oggetto implementa l'interfaccia IConvertible, il tipo di variante COM viene determinato in fase di esecuzione dal valore dell'enumerazione TypeCode restituita dal metodo IConvertible.GetTypeCode.

La tabella seguente illustra i possibili valori per l'enumerazione TypeCode e il tipo di variante COM corrispondente per ogni valore.

TypeCode Tipo di variante 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 supportato. VT_INT
Non supportato. VT_UINT
Non supportato. VT_ARRAY
Non supportato. VT_RECORD
Non supportato. VT_CY
Non supportato. VT_VARIANT

Il valore della variante COM viene determinato chiamando l'interfaccia IConvertible.To Type, dove To Type è la routine di conversione che corrisponde al tipo restituito da IConvertible.GetTypeCode. Ad esempio, il marshalling di un oggetto che restituisce TypeCode.Double da IConvertible.GetTypeCode viene effettuato come variante COM di tipo VT_R8. Per ottenere il valore della variante (archiviata nel campo dblVal della variante COM), è possibile eseguire il cast all'interfaccia IConvertible e chiamare il metodo ToDouble.

Marshalling da variante a oggetto

Quando si effettua il marshalling di una variante a un oggetto, il tipo e a volte il valore della variante di cui viene effettuato il marshalling determinano il tipo dell'oggetto prodotto. La tabella seguente identifica ogni tipo di variante e il tipo di oggetto corrispondente creato dal gestore di marshalling quando una variante viene passata da COM a .NET Framework.

Tipo di variante COM Object type
VT_EMPTY Riferimento all'oggetto null (Nothing in Visual Basic).
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject o null se (pdispVal == null)
VT_UNKNOWN System.__ComObject o null se (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 Tipo valore boxed corrispondente.
VT_VARIANT Non supportato.

I tipi di variante passati da COM al codice gestito e quindi di nuovo a COM potrebbero non conservare lo stesso tipo di variante per tutta la durata della chiamata. Si consideri che cosa accade quando una variante di tipo VT_DISPATCH viene passata da COM a .NET Framework. Durante il marshalling, la variante viene convertita in System.Object. Se Object viene quindi passato nuovamente a COM, ne viene effettuato il marshalling a una variante di tipo VT_UNKNOWN. Non esiste garanzia che la variante prodotta quando viene effettuato il marshalling di un oggetto dal codice gestito a COM sarà dello stesso tipo della variante usata inizialmente per produrre l'oggetto.

Marshalling di varianti ByRef

Anche se le varianti in sé possono essere passate in base al valore o al riferimento, è anche possibile usare il flag VT_BYREF con qualsiasi variante per indicare che i contenuti della variante verranno passati in base al riferimento invece che al valore. La differenza tra il marshalling delle varianti per riferimento e il marshalling di una variante con il flag VT_BYREF impostato può risultare poco chiara. La figura seguente illustra le differenze:

Diagramma che illustra la variante passata allo stack. Varianti passate per valore e per riferimento

Comportamento predefinito per il marshalling di oggetti e varianti per valore

  • Quando si passano oggetti dal codice gestito a COM, i contenuti dell'oggetto vengono copiati in una nuova variante creata dal gestore di marshalling, usando le regole definite in Marshalling da oggetto a variante. Le modifiche apportate alla variante sul lato non gestito non vengono propagate all'oggetto originale al ritorno dalla chiamata.

  • Quando si passano le varianti da COM al codice gestito, i contenuti della variante vengono copiati in un nuovo oggetto creato, usando le regole definite in Marshalling da variante a oggetto. Le modifiche apportate all'oggetto sul lato gestito non vengono propagate alla variante originale al ritorno dalla chiamata.

Comportamento predefinito per il marshalling di oggetti e varianti per riferimento

Per propagare le modifiche al chiamante, i parametri devono essere passati per riferimento. È ad esempio possibile usare la parola chiave ref in C# (o ByRef nel codice gestito di Visual Basic) per passare i parametri per riferimento. In COM i parametri per riferimento vengono passati usando un puntatore, ad esempio una variante *.

  • Quando si passa un oggetto a COM per riferimento, il gestore di marshalling crea una nuova variante e copia i contenuti del riferimento all'oggetto nella variante prima che venga effettuata la chiamata. La variante viene passata alla funzione non gestita in cui l'utente può modificare i contenuti della variante. Al ritorno dalla chiamata, le modifiche apportate alla variante sul lato non gestito vengono propagate all'oggetto originale. Se il tipo della variante è diverso dal tipo della variante passata alla chiamata, le modifiche vengono propagate a un oggetto di tipo diverso, ovvero il tipo dell'oggetto passato nella chiamata può essere diverso dal tipo dell'oggetto restituito dalla chiamata.

  • Quando si passa una variante al codice gestito per riferimento, il gestore di marshalling crea un nuovo oggetto e copia i contenuti della variante nell'oggetto prima di effettuare la chiamata. Un riferimento all'oggetto viene passato alla funzione non gestita, in cui l'utente può modificare l'oggetto. Al ritorno dalla chiamata, le modifiche apportate all'oggetto di riferimento vengono propagate alla variante originale. Se il tipo dell'oggetto è diverso dal tipo dell'oggetto passato alla chiamata, il tipo della variante originale viene modificato e il valore viene propagato nella variante. Anche in questo caso il tipo della variante passata nella chiamata può essere diverso dal tipo della variante restituita dalla chiamata.

Comportamento predefinito per il marshalling di una variante con il flag VT_BYREF impostato

  • Una variante che viene passata al codice gestito per valore può avere il flag VT_BYREF impostato per indicare che la variante contiene un riferimento invece che un valore. In questo caso, viene ancora effettuato il marshalling della variante a un oggetto perché la variante viene passata per valore. Il gestore di marshalling dereferenzia automaticamente i contenuti della variante e la copia in un nuovo oggetto creato prima di eseguire la chiamata. L'oggetto viene quindi passato nella funzione gestita, tuttavia, al ritorno dalla chiamata, l'oggetto non viene propagato nella variante originale. Le modifiche apportate all'oggetto gestito vengono perse.

    Attenzione

    Non è possibile modificare il valore di una variante passata per valore, anche se la variante ha il flag VT_BYREF impostato.

  • Una variante che viene passata al codice gestito per riferimento può anche avere il flag VT_BYREF impostato per indicare che la variante contiene un altro riferimento. In questo caso, viene effettuato il marshalling della variante a un oggetto ref perché la variante viene passata per riferimento. Il gestore di marshalling dereferenzia automaticamente i contenuti della variante e la copia in un nuovo oggetto creato prima di eseguire la chiamata. Al ritorno dalla chiamata, il valore dell'oggetto viene propagato al riferimento nella variante originale solo se l'oggetto è dello stesso tipo dell'oggetto passato, ovvero la propagazione non modifica il tipo di una variante con il flag VT_BYREF impostato. Se il tipo dell'oggetto viene modificato durante la chiamata, al ritorno dalla chiamata viene generata un'eccezione InvalidCastException.

La tabella seguente riepiloga le regole di propagazione per varianti e oggetti.

Da Per Modifiche propagate
Variante v Oggetto o Mai
Oggetto o Variante v Mai
Variante * pv Oggetto di riferimento o Sempre
Oggetto di riferimento o Variante * pv Sempre
Variante v (VT_BYREF | VT_*) Oggetto o Mai
Variante v (VT_BYREF | VT_) Oggetto di riferimento o Solo se il tipo non è stato modificato.

Vedi anche