Serialización predeterminada para objetos
Los parámetros y campos de tipo System.Object pueden exponerse a código no administrado como uno de los siguientes tipos:
Una variante cuando el objeto es un parámetro.
Una interfaz cuando el objeto es un campo de estructura.
Solo la interoperabilidad COM admite la serialización para tipos de objeto. El comportamiento predeterminado consiste en serializar los objetos en variantes de COM. Estas reglas se aplican solo al tipo Object y no se aplican a objetos fuertemente tipados que se derivan de la clase Object.
Opciones de serialización
En la tabla siguiente se muestran las opciones de serialización para el tipo de datos Object. El atributo MarshalAsAttribute proporciona varios valores de enumeración UnmanagedType para serializar objetos.
Tipo de enumeración | Descripción de formato no administrado |
---|---|
UnmanagedType.Struct (valor predeterminado para parámetros) |
Una variante de estilo COM. |
UnmanagedType.Interface | Una interfaz IDispatch si es posible; de lo contrario, una interfaz IUnknown. |
UnmanagedType.IUnknown (valor predeterminado para campos) |
Una interfaz IUnknown. |
UnmanagedType.IDispatch | Una interfaz IDispatch. |
En el ejemplo siguiente se muestra la definición de interfaz administrada para 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();
}
En el código siguiente se exporta la interfaz MarshalObject
a una biblioteca de tipos.
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
El serializador de interoperabilidad libera automáticamente cualquier objeto asignado dentro de la variante tras la llamada.
En el ejemplo siguiente se muestra un tipo de valor con formato.
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;
}
En el código siguiente se exporta el tipo con formato a una biblioteca de tipos.
struct ObjectHolder {
VARIANT o1;
IDispatch *o2;
}
Serialización de Object en Interface
Cuando un objeto se expone a COM como una interfaz, esa interfaz es la interfaz de clase para el tipo administrado Object (la interfaz _Object). Esta interfaz tiene el tipo de IDispatch (UnmanagedType) o IUnknown (UnmanagedType.IUnknown) en la biblioteca de tipos resultante. Los clientes COM pueden invocar dinámicamente los miembros de la clase administrada o cualquier miembro implementado por sus clases derivadas a través de la interfaz _Object. El cliente puede llamar a QueryInterface para obtener cualquier otra interfaz implementada explícitamente por el tipo administrado.
Serialización de Object en Variant
Cuando un objeto se serializa en una variante, el tipo de variante interno se determina en tiempo de ejecución, según las reglas siguientes:
Si la referencia de objeto es NULL (Nothing en Visual Basic), el objeto se serializa en una variante del tipo VT_EMPTY.
Si el objeto es una instancia de cualquier tipo enumerado en la tabla siguiente, el tipo de variante resultante se determina mediante las reglas integradas en el serializador y mostradas en la tabla.
Otros objetos que necesiten controlar explícitamente el comportamiento de serialización pueden implementar la interfaz IConvertible. En ese caso, el tipo de variante se determina por el código de tipo devuelto por el método IConvertible.GetTypeCode. En caso contrario, el objeto se serializa como una variante de tipo VT_UNKNOWN.
Serialización de tipos de sistema en Variant
En la tabla siguiente se muestran los tipos de objeto administrados y sus tipos de variante COM correspondientes. Estos tipos solo se convierten cuando la firma del método al que se llama es de tipo System.Object.
Tipo de objeto | Tipo de variante COM |
---|---|
Referencia de objeto nula (Nothing en 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 |
Con la interfaz MarshalObject
definida en el ejemplo anterior, en el ejemplo de código siguiente se muestra cómo pasar varios tipos de variantes a un servidor 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.
Los tipos COM que no tienen tipos administrados correspondientes se pueden serializar mediante clases contenedoras como ErrorWrapper, DispatchWrapper, UnknownWrapper y CurrencyWrapper. En el ejemplo de código siguiente se muestra cómo usar estos contenedores para pasar varios tipos de variantes a un servidor 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)));
Las clases contenedoras se definen en el espacio de nombres System.Runtime.InteropServices.
Serialización de la interfaz IConvertible en Variant
Otros tipos distintos a los de la sección anterior pueden controlar cómo se serializan mediante la implementación de la interfaz IConvertible. Si el objeto implementa la interfaz IConvertible, el tipo de variante COM se determina en tiempo de ejecución por el valor de la enumeración TypeCode devuelto desde el método IConvertible.GetTypeCode.
En la tabla siguiente se muestran los valores posibles para la enumeración TypeCode y el tipo de variante COM correspondiente para cada valor.
TypeCode | Tipo de 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 |
No se admite. | VT_INT |
No se admite. | VT_UINT |
No se admite. | VT_ARRAY |
No se admite. | VT_RECORD |
No se admite. | VT_CY |
No se admite. | VT_VARIANT |
El valor de la variante COM se determina realizando una llamada a la interfaz IConvertible.To Tipo, donde To Tipo es la rutina de conversión que se corresponde con el tipo que se devolvió desde IConvertible.GetTypeCode. Por ejemplo, un objeto que devuelve TypeCode.Double de IConvertible.GetTypeCode se serializa como una variante COM de tipo VT_R8. Puede obtener el valor de la variante (almacenado en el campo dblVal de la variante COM) si convierte a la interfaz IConvertible y llama al método ToDouble.
Serialización de Variant en Object
Al serializar una variante en un objeto, el tipo y, a veces, el valor de la variante serializada determina el tipo de objeto generado. En la siguiente tabla se identifica cada tipo de variante y el tipo de objeto correspondiente que el serializador crea cuando se pasa una variante desde COM a .NET Framework.
Tipo de variante COM | Tipo de objeto |
---|---|
VT_EMPTY | Referencia de objeto nula (Nothing en Visual Basic). |
VT_NULL | System.DBNull |
VT_DISPATCH | System.__ComObject o null si (pdispVal == null) |
VT_UNKNOWN | System.__ComObject o 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 | Tipo de valor de conversión boxing correspondiente. |
VT_VARIANT | No se admite. |
Es posible que los tipos de variante que se pasan desde COM a código administrado y después otra vez a COM no conserven el mismo tipo de variante para la duración de la llamada. Tenga en cuenta lo que sucede cuando una variante de tipo VT_DISPATCH se pasa desde COM a .NET Framework. Durante la serialización, la variante se convierte en un System.Object. Si después se pasa Object a COM, se vuelve a serializar en una variante de tipo VT_UNKNOWN. No hay ninguna garantía de que la variante que se genera cuando se serializa un objeto desde código administrado a COM sea del mismo tipo que la variante usada inicialmente para generar el objeto.
Serialización de variantes ByRef
Aunque las variantes se pueden pasar por valor o por referencia, la marca VT_BYREF también se puede usar con cualquier tipo de variante para indicar que el contenido de la variante se está pasando por referencia en lugar de por valor. La diferencia entre serializar variantes por referencia y serializar una variante con la marca VT_BYREF establecida puede resultar confusa. En la siguiente ilustración se explican las diferencias:
Variantes pasadas por valor y por referencia
Comportamiento predeterminado para la serialización de objetos y variantes por valor
Cuando se pasan objetos desde código administrado a COM, el contenido del objeto se copia en una nueva variante creada por el serializador mediante las reglas definidas en Serialización de Object en Variant. Los cambios realizados en la variante en el lado no administrado no se propagan al objeto original en la devolución de la llamada.
Al pasar variantes de COM a código administrado, el contenido de la variante se copia en un objeto recién creado, con las reglas definidas en Serialización de Variant en Object. Los cambios realizados en el objeto en el lado no administrado no se propagan a la variante original en la devolución de la llamada.
Comportamiento predeterminado para la serialización de objetos y variantes por referencia
Para propagar los cambios de vuelta al autor de la llamada, los parámetros deben pasarse por referencia. Por ejemplo, puede usar la palabra clave ref de C# (o ByRef en código administrado de Visual Basic) para pasar parámetros por referencia. En COM, los parámetros de referencia se pasan con un puntero como una variante *.
Cuando se pasa un objeto a COM por referencia, el serializador crea una variante y copia el contenido de la referencia de objeto en la variante antes de que se realice la llamada. La variante se pasa a la función no administrada, donde el usuario tiene libertad para cambiar el contenido de la variante. En la devolución de la llamada, los cambios realizados en la variante en el lado no administrado se propagan al objeto original. Si el tipo de la variante difiere del tipo de la variante que se pasa a la llamada, los cambios se propagan a un objeto de un tipo diferente. Es decir, el tipo del objeto pasado en la llamada puede diferir del tipo del objeto devuelto de la llamada.
Cuando se pasa una variante por referencia a código administrado, el serializador crea un objeto y copia el contenido de la variante en el objeto antes de que se realice la llamada. Se pasa una referencia al objeto a la función administrada, donde el usuario tiene libertad para cambiar el contenido del objeto. En la devolución de la llamada, los cambios realizados en el objeto al que se hace referencia se propagan a la variante original. Si el tipo del objeto difiere del tipo del objeto pasado en la llamada, el tipo de la variante original se cambia y el valor se propaga a la variante. Como antes, el tipo de la variante pasada en la llamada puede diferir del tipo de la variante devuelta de la llamada.
Comportamiento predeterminado para serializar una variante con la marca VT_BYREF establecida
Una variante que se pasa a código administrado por valor puede tener la marca VT_BYREF establecida para indicar que la variante contiene una referencia en lugar de un valor. En este caso, la variante se sigue serializando en un objeto porque la variante se pasa por valor. El serializador desreferencia automáticamente el contenido de la variante y la copia en un objeto recién creado antes de realizar la llamada. Después, el objeto se pasa a la función administrada; pero en la devolución de la llamada, el objeto no se propaga a la variante original. Se perderán los cambios realizados en el objeto administrado.
Precaución
No hay ninguna manera de cambiar el valor de una variante pasada por valor, aunque la variante tenga establecida la marca VT_BYREF.
Una variante que se pasa a código administrado por referencia también puede tener la marca VT_BYREF establecida para indicar que la variante contiene otra referencia. En ese caso, la variante se serializa en un objeto ref porque la variante se pasa por referencia. El serializador desreferencia automáticamente el contenido de la variante y la copia en un objeto recién creado antes de realizar la llamada. En la devolución de la llamada, el valor del objeto se propaga a la referencia dentro de la variante original solo si el objeto es del mismo tipo que el objeto que se pasa. Es decir, la propagación no cambia el tipo de una variante con la marca VT_BYREF establecida. Si el tipo del objeto se cambia durante la llamada, se inicia una excepción InvalidCastException en la devolución de la llamada.
En la tabla siguiente se resumen las reglas de propagación para variantes y objetos.
De | En | Los cambios se propagan |
---|---|---|
Variante v | Objeto o | Nunca |
Objeto o | Variante v | Nunca |
Variante * pv | Objeto de referencia o | Siempre |
Objeto de referencia o | Variante * pv | Siempre |
Variant v (VT_BYREF | VT_*) | Objeto o | Nunca |
Variant v (VT_BYREF | VT_) | Objeto de referencia o | Solo si el tipo no ha cambiado. |