Share via


Marshalling predefinito per gli oggetti

Aggiornamento: novembre 2007

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

  • Un variant quando l'oggetto è un parametro.

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

Il marshalling per i tipi di oggetto è supportato solo dall'interoperabilità COM. Il comportamento predefinito prevede il marshalling di oggetti su variant COM. Queste regole si applicano solo al tipo Object e non a oggetti fortemente tipizzati che derivano dalla classe Object.

In questo argomento vengono fornite le seguenti informazioni aggiuntive sul marshalling dei tipi di oggetto:

  • Opzioni di marshalling

  • Marshalling di un oggetto su un'interfaccia

  • Marshalling di un oggetto su un variant

  • Marshalling di un variant su un oggetto

  • Marshalling di variant ByRef

Opzioni di marshalling

Nella tabella seguente sono illustrate le opzioni di marshalling per il tipo di dati Object. L'attributo MarshalAsAttribute fornisce diversi valori dell'enumerazione UnmanagedType per il marshalling di oggetti.

Tipo di enumerazione

Descrizione del formato non gestito

UnmanagedType.Struct

(valore predefinito per i parametri)

Variant di tipo COM.

UnmanagedType.Interface

Interfaccia IDispatch, se possibile; in caso contrario, IUnknown.

UnmanagedType.IUnknown

(valore predefinito per i campi)

Interfaccia IUnknown.

UnmanagedType.IDispatch

Interfaccia IDispatch.

Nell'esempio che segue viene mostrata 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();
}

Nel codice seguente l'interfaccia MarshalObject viene esportata 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:

Tramite il gestore di marshalling di interoperabilità, qualsiasi oggetto allocato all'interno del variant viene liberato automaticamente dopo la chiamata.

Nell'esempio riportato di seguito viene mostrato un tipo di 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;
}

Nel codice seguente il tipo formattato viene esportato in una libreria dei tipi.

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

Marshalling di un oggetto su un'interfaccia

Quando un oggetto è esposto a COM come interfaccia, quest'ultima rappresenta l'interfaccia della classe del tipo gestito Object (interfaccia _Object). Questa interfaccia è tipizzata come IDispatch (UnmanagedType.IDispatch) o IUnknown (UnmanagedType.IUnknown) nella libreria dei tipi risultante. I client COM possono richiamare in modo dinamico i membri della classe gestita o i membri implementati dalle classi derivate mediante l'interfaccia _Object. Il client può inoltre chiamare QueryInterface per ottenere qualsiasi altra interfaccia implementata in modo esplicito dal tipo gestito.

Marshalling di un oggetto su un variant

Quando si esegue il marshalling di un oggetto su un variant, il tipo di variant interno è determinato in fase di esecuzione, in base alle regole seguenti:

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

  • Se l'oggetto è un'istanza di uno dei tipi elencati nella tabella riportata di seguito, il tipo di variant risultante è determinato dalle regole incorporate nel gestore di marshalling e illustrate nella tabella.

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

Marshalling dei tipi del sistema su variant

Nella tabella riportata di seguito vengono mostrati i tipi di oggetti gestiti e i tipi di variant COM corrispondenti. Questi tipi vengono convertiti solo quando la firma del metodo chiamato è di tipo System.Object.

Tipo di oggetto

Tipo di variant COM

Riferimento a un 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

Mediante l'utilizzo dell'interfaccia MarshalObject definita nell'esempio precedente, nell'esempio di codice seguente viene mostrato come passare vari tipi di variant 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 eseguire il marshalling dei tipi COM privi di tipi gestiti corrispondenti mediante classi wrapper quali ErrorWrapper, DispatchWrapper, UnknownWrapper e CurrencyWrapper. Nell'esempio di codice riportato di seguito viene mostrato come utilizzare questi wrapper per passare diversi tipi di variant 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 sono definite nello spazio dei nomi System.Runtime.InteropServices.

Marshalling dell'interfaccia IConvertible su un variant

Il marshalling dei tipi non elencati nella sezione precedente può essere controllato implementando l'interfaccia IConvertible. Se l'interfaccia IConvertible viene implementata dall'oggetto, il tipo di variant COM viene determinato in fase di esecuzione dal valore dell'enumerazione TypeCode restituito dal metodo IConvertible.GetTypeCode.

Nella tabella riportata di seguito vengono mostrati i valori possibili per l'enumerazione TypeCode e il tipo di variant COM corrispondente per ciascun valore.

TypeCode

Tipo di 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 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 del variant COM viene determinato chiamando l'interfaccia IConvertible.ToTipo, dove ToTipo rappresenta la routine di conversione corrispondente al tipo restituito da IConvertible.GetTypeCode. Se un oggetto restituisce TypeCode.Double da IConvertible.GetTypeCode, viene eseguito il marshalling dell'oggetto come variant COM di tipo VT_R8. È possibile ottenere il valore del variant, memorizzato nel campo dblVal del variant COM, mediante il cast sull'interfaccia IConvertible e la chiamata al metodo ToDouble.

Marshalling di un variant su un oggetto

Quando si esegue il marshalling di un variant su un oggetto, il tipo di oggetto prodotto viene determinato dal tipo, e talvolta dal valore, di tale variant. Nella tabella riportata di seguito viene specificato ogni tipo di variant e il tipo di oggetto corrispondente creato dal gestore di marshalling quando si passa un variant da COM a .NET Framework.

Tipo di variant COM

Tipo di oggetto

VT_EMPTY

Riferimento a un 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 di valore boxed corrispondente.

VT_VARIANT

Non supportato.

È possibile che i tipi di variant passati da COM al codice gestito e quindi restituiti a COM non mantengano lo stesso tipo di variant per la durata della chiamata. Si consideri cosa succede quando si passa un variant di tipo VT_DISPATCH da COM a .NET Framework. Nel corso del marshalling, il variant viene convertito in un oggetto System.Object. Se Object viene restituito a COM, se ne esegue il marshalling su un variant di tipo VT_UNKNOWN. Non esistono garanzie che il variant prodotto quando si esegue il marshalling di un oggetto dal codice gestito su COM sia dello stesso tipo del variant inizialmente utilizzato per produrre l'oggetto.

Marshalling di variant ByRef

Benché i variant possano essere passati per valore o per riferimento, il flag VT_BYREF può anche essere utilizzato con qualsiasi tipo di variant per indicare che il contenuto del variant viene passato per riferimento e non per valore. La differenza tra il marshalling di variant per riferimento e quello di un variant con il flag VT_BYREF impostato può essere poco chiara. Nell'illustrazione riportata di seguito vengono spiegate le differenze.

Variant passati per valore e per riferimento

Variante passata allo stack

Comportamento predefinito per il marshalling di oggetti e variant per valore

  • Quando si passa un oggetto dal codice gestito a COM, il contenuto dello stesso viene copiato in un nuovo variant creato dal gestore di marshalling mediante le regole definite nella sezione Marshalling di un oggetto su un variant. Le modifiche apportate al variant sul lato non gestito non vengono propagate all'oggetto originale dopo la chiamata.

  • Quando si passano variant da COM al codice gestito, il contenuto del variant viene copiato in un oggetto appena creato mediante le regole definite nella sezione Marshalling di un variant su un oggetto. Le modifiche apportate all'oggetto sul lato gestito non vengono propagate al variant originale dopo la chiamata.

Comportamento predefinito per il marshalling di oggetti e variant per riferimento

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

  • Quando si passa un oggetto a COM per riferimento, il gestore di marshalling crea un nuovo variant nel quale copia il contenuto del riferimento all'oggetto prima di effettuare la chiamata. Il variant viene passato alla funzione non gestita in cui l'utente può modificarne il contenuto. Dopo la chiamata, le modifiche apportate al variant sul lato non gestito vengono propagate all'oggetto originale. Se il tipo del variant è diverso da quello passato alla chiamata, le modifiche vengono propagate a un oggetto di tipo diverso, ossia il tipo dell'oggetto passato nella chiamata può essere diverso da quello restituito dalla chiamata.

  • Quando si passa un variant al codice gestito per riferimento, il gestore di marshalling crea un nuovo oggetto nel quale copia il contenuto del variant prima di effettuare la chiamata. Un riferimento all'oggetto viene passato alla funzione gestita, in cui l'utente può modificare l'oggetto. Dopo la chiamata, le modifiche apportate all'oggetto di riferimento vengono propagate al variant originale. Se il tipo dell'oggetto è diverso da quello passato alla chiamata, viene modificato il tipo del variant originale e il valore viene propagato al variant. Anche in questo caso, il tipo del variant passato nella chiamata può quindi essere diverso da quello restituito dalla chiamata.

Comportamento predefinito per il marshalling di un variant con il flag VT_BYREF impostato

  • Un variant passato al codice gestito per valore può avere il flag VT_BYREF impostato per indicare che contiene un riferimento anziché un valore. In questo caso, viene ancora eseguito il marshalling del variant su un oggetto, in quanto il variant è passato per valore. Il gestore di marshalling annulla automaticamente i riferimenti al contenuto del variant e lo copia in un oggetto appena creato prima di effettuare la chiamata. L'oggetto viene quindi passato alla funzione gestita. Tuttavia, dopo la chiamata, l'oggetto non viene propagato al variant originale. Le modifiche apportate all'oggetto gestito vengono perse.

    Attenzione:

    Il valore di un variant passato per valore non può essere modificato in alcun modo, anche se nel variant è stato impostato il flag VT_BYREF.

  • Un variant passato al codice gestito per riferimento può anche avere il flag VT_BYREF impostato per indicare che contiene un altro riferimento. In questo caso, viene eseguito il marshalling del variant su un oggetto ref, in quanto il variant è passato per riferimento. Il gestore di marshalling annulla automaticamente i riferimenti al contenuto del variant e lo copia in un oggetto appena creato prima di effettuare la chiamata. Dopo la chiamata, il valore dell'oggetto viene propagato al riferimento all'interno del variant originale solo se l'oggetto è dello stesso tipo di quello passato. Con la propagazione non viene modificato il tipo di un variant con il flag VT_BYREF impostato. Se il tipo dell'oggetto viene modificato nel corso della chiamata, dopo la chiamata viene generato un oggetto InvalidCastException.

Nella tabella riportata di seguito sono riassunte le regole di propagazione per variant e oggetti.

Da

Per

Modifiche propagate

Variant v

Object o

Mai

Object o

Variant v

Mai

Variant *pv

Oggetto di riferimento o

Sempre

Oggetto di riferimento o

Variant *pv

Sempre

Variant v(VT_BYREF|VT_*)

Object o

Mai

Variant v(VT_BYREF|VT_)

Oggetto di riferimento o

Solo se il tipo non è stato modificato

Vedere anche

Concetti

Tipi copiabili e non copiabili

Attributi direzionali

Copia e blocco

Altre risorse

Comportamento di marshalling predefinito