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 mediante una llamada a la interfaz IConvertible.ToTipo, donde ToTipo 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:

Diagram that shows variant passed on the stack. 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
Variantev Objetoo Nunca
Objetoo Variantev Nunca
Variante*pv Objeto de refo Siempre
Objeto de refo Variante*pv Siempre
Variantev(VT_BYREF|VT_*) Objetoo Nunca
Variantev(VT_BYREF|VT_) Objeto de refo Solo si el tipo no ha cambiado.

Vea también