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:
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
- Comportamento di marshalling predefinito
- Blittable and Non-Blittable Types (Tipi copiabili da BLT e non copiabili da BLT)
- Attributi direzionali
- Copia e blocco