Marshaling padrão para objetos

Os parâmetros e os campos tipados como System.Object podem ser expostos para um código não gerenciado como um dos seguintes tipos:

  • Uma variante quando o objeto é um parâmetro.

  • Uma interface quando o objeto é um campo de estrutura.

Somente a interoperabilidade COM dá suporte ao marshaling de tipos de objeto. O comportamento padrão é realizar marshaling de objetos para variantes COM. Essas regras se aplicam somente ao tipo Object e não se aplicam a objetos fortemente tipados derivados da classe Object.

Opções de marshaling

A tabela a seguir mostra as opções de marshaling para o tipo de dados Object. O atributo MarshalAsAttribute fornece vários valores de enumeração UnmanagedType para realizar marshaling de objetos.

Tipo de enumeração Descrição do formato não gerenciado
UnmanagedType.Struct

(padrão para parâmetros)
Uma variante de estilo COM.
UnmanagedType.Interface Uma interface IDispatch, se possível; caso contrário, uma interface IUnknown.
UnmanagedType.IUnknown

(padrão para campos)
Uma interface IUnknown.
UnmanagedType.IDispatch Uma interface IDispatch.

O exemplo a seguir mostra a definição de interface gerenciada 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();
}

O código a seguir exporta a interface MarshalObject para uma 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)
}

Observação

O marshaller de interoperabilidade libera automaticamente qualquer objeto alocado dentro na variante após a chamada.

O exemplo a seguir mostra um tipo de valor formatado.

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

O código a seguir exporta o tipo formatado para uma biblioteca de tipos.

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

Realizando marshaling de objeto para interface

Quando um objeto é exposto para o COM como uma interface, essa interface é a interface de classe do tipo gerenciado Object (a interface _Object). Essa interface é tipada como um IDispatch (UnmanagedType) ou um IUnknown (UnmanagedType.IUnknown) na biblioteca de tipos resultante. Os clientes COM podem invocar dinamicamente os membros da classe gerenciada ou todos os membros implementados por suas classes derivadas por meio da interface _Object. O cliente também pode chamar QueryInterface para obter qualquer outra interface implementada explicitamente pelo tipo gerenciado.

Realizando marshaling de objeto para variante

Quando um objeto tem o marshaling realizado como uma variante, o tipo de variante interno é determinado em tempo de execução, de acordo com as seguintes regras:

  • Se a referência de objeto for nula (Nothing no Visual Basic), o objeto terá o marshaling realizado como uma variante do tipo VT_EMPTY.

  • Se o objeto for uma instância de qualquer tipo listado na tabela a seguir, o tipo de variante resultante será determinado pelas regras internas do marshaler e mostrado na tabela.

  • Outros objetos que precisam controlar explicitamente o comportamento de marshaling podem implementar a interface IConvertible. Nesse caso, o tipo de variante é determinado pelo código do tipo retornado do método IConvertible.GetTypeCode. Caso contrário, o objeto terá o marshaling realizado como uma variante do tipo VT_UNKNOWN.

Realizando marshaling de tipos do sistema para variante

A tabela a seguir mostra os tipos de objeto gerenciados e seus tipos de variante COM correspondentes. Esses tipos são convertidos somente quando a assinatura do método que está sendo chamado é do tipo System.Object.

Tipo de objeto Tipo de variante COM
Referência de objeto nulo (Nothing no Visual Basic). VT_EMPTY
System.DBNull VT_NULL
System.Runtime.InteropServices.ErrorWrapper VT_ERROR
System.Reflection.Missing VT_ERROR com 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 a interface MarshalObject definida no exemplo anterior, o exemplo de código a seguir demonstra como passar vários tipos de variantes para um 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.

Tipos COM que não têm tipos gerenciados correspondentes podem ter o marshaling realizado usando classes wrapper como ErrorWrapper, DispatchWrapper, UnknownWrapper e CurrencyWrapper. O exemplo de código a seguir demonstra como usar esses wrappers para passar vários tipos de variantes para um 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)));

As classes wrapper são definidas no namespace System.Runtime.InteropServices.

Realizando marshaling da interface IConvertible para variante

Outros tipos além daqueles listados na seção anterior podem controlar seu marshaling com a implementação da interface IConvertible. Se o objeto implementar a interface IConvertible, o tipo de variante COM será determinado em tempo de execução pelo valor da enumeração TypeCode retornado do método IConvertible.GetTypeCode.

A tabela a seguir mostra os valores possíveis para a enumeração TypeCode e o tipo de variante COM correspondente 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
Não há suporte. VT_INT
Não há suporte. VT_UINT
Não há suporte. VT_ARRAY
Não há suporte. VT_RECORD
Não há suporte. VT_CY
Não há suporte. VT_VARIANT

O valor da variante COM é determinado com uma chamada à interface IConvertible.ToType, em que ToType é a rotina de conversão que corresponde ao tipo retornado do IConvertible.GetTypeCode. Por exemplo, um objeto que retorna TypeCode.Double de IConvertible.GetTypeCode tem o marshaling realizado como uma variante COM do tipo VT_R8. É possível obter o valor da variante (armazenado no campo dblVal da variante COM) com a conversão para a interface IConvertible e uma chamada ao método ToDouble.

Realizando marshaling de variante para objeto

Ao realizar marshaling de uma variante para um objeto, o tipo e, às vezes, o valor, da variante com marshaling determina o tipo de objeto produzido. A tabela a seguir identifica cada tipo de variante e o tipo de objeto correspondente criado pelo marshaler quando uma variante é passada do COM para o .NET Framework.

Tipo de variante COM Tipo de objeto
VT_EMPTY Referência de objeto nulo (Nothing no Visual Basic).
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject ou nulo se (pdispVal == null)
VT_UNKNOWN System.__ComObject ou nulo 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 de valor demarcado correspondente.
VT_VARIANT Não há suporte.

Os tipos de variante passados do COM para o código gerenciado e, em seguida, novamente para o COM podem não manter o mesmo tipo de variante durante a chamada. Considere o que acontece quando uma variante do tipo VT_DISPATCH é passada do COM para o .NET Framework. Durante o marshaling, a variante é convertida em um System.Object. Se o Object for então passado novamente para o COM, ele terá o marshaling realizado novamente como uma variante do tipo VT_UNKNOWN. Não há nenhuma garantia de que a variante produzida quando um objeto tem o marshaling realizado de um código gerenciado para o COM terá o mesmo tipo de variante usado inicialmente para produzir o objeto.

Realizando marshaling de variantes de ByRef

Embora as próprias variantes possam ser passadas por valor ou por referência, o sinalizador VT_BYREF também pode ser usado com qualquer tipo de variante para indicar que o conteúdo da variante está sendo passado por referência, em vez de por valor. A diferença entre realizar marshaling de variantes por referência e realizar marshaling de uma variante com o sinalizador VT_BYREF definido pode ser confusa. A seguinte ilustração esclarece as diferenças:

Diagram that shows variant passed on the stack. Variantes passadas por valor e por referência

Comportamento padrão de marshaling de objetos e variantes por valor

  • Ao passar objetos do código gerenciado para o COM, o conteúdo do objeto é copiado para uma nova variante criada pelo marshaler, usando as regras definidas em Realizando marshaling de objeto para variante. As alterações feitas na variante no lado não gerenciado não são propagadas novamente para o objeto original após o retorno da chamada.

  • Ao passar variantes do COM para o código gerenciado, o conteúdo da variante é copiado para um objeto recém-criado, usando as regras definidas em Realizando marshaling de variante para objeto. As alterações feitas no objeto no lado gerenciado não são propagadas novamente para a variante original após o retorno da chamada.

Comportamento padrão de marshaling de objetos e variantes por referência

Para propagar as alterações novamente para o chamador, os parâmetros devem ser passados por referência. Por exemplo, use a palavra-chave ref no C# (ou ByRef no código gerenciado do Visual Basic) para passar parâmetros por referência. No COM, os parâmetros de referência são passados usando um ponteiro, como uma variante *.

  • Ao passar um objeto para o COM por referência, o marshaler cria uma nova variante e copia o conteúdo da referência de objeto para a variante antes que a chamada seja feita. A variante é passada para a função não gerenciada, na qual o usuário é livre para alterar o conteúdo da variante. Após o retorno da chamada, todas as alterações feitas na variante no lado não gerenciado são propagadas novamente para o objeto original. Se o tipo da variante for diferente do tipo da variante passada para a chamada, as alterações serão propagadas novamente para um objeto de um tipo diferente. Ou seja, o tipo do objeto passado para a chamada pode ser diferente do tipo do objeto retornado da chamada.

  • Ao passar uma variante para um código gerenciado por referência, o marshaler cria um novo objeto e copia o conteúdo da variante para o objeto antes de fazer a chamada. Uma referência ao objeto é passada para a função gerenciada, em que o usuário é livre para alterar o objeto. Após o retorno da chamada, todas as alterações feitas no objeto referenciado são propagadas novamente para a variante original. Se o tipo do objeto for diferente do tipo do objeto passado para a chamada, o tipo da variante original será alterado e o valor será propagado novamente para a variante. Novamente, o tipo da variante passado para a chamada pode ser diferente do tipo da variante retornado da chamada.

Comportamento padrão de marshaling de uma variante com o sinalizador VT_BYREF definido

  • Uma variante que está sendo passada para o código gerenciado por valor pode ter o sinalizador VT_BYREF definido para indicar que a variante contém uma referência, em vez de um valor. Nesse caso, a variante ainda tem o marshaling realizado para um objeto porque a variante está sendo passada por valor. O marshaler desreferencia automaticamente o conteúdo da variante e o copia para um objeto recém-criado antes de fazer a chamada. Em seguida, o objeto é passado para a função gerenciada; no entanto, após o retorno da chamada, o objeto não é propagado novamente para a variante original. As alterações feitas no objeto gerenciado são perdidas.

    Cuidado

    Não há nenhuma maneira de alterar o valor de uma variante passada por valor, mesmo se a variante tiver um sinalizador VT_BYREF definido.

  • Uma variante que está sendo passada para o código gerenciado por referência também pode ter o sinalizador VT_BYREF definido para indicar que a variante contém outra referência. Nesse caso, a variante tem o marshaling realizado para um objeto ref porque a variante está sendo passada por referência. O marshaler desreferencia automaticamente o conteúdo da variante e o copia para um objeto recém-criado antes de fazer a chamada. Após o retorno da chamada, o valor do objeto é propagado novamente para a referência dentro da variante original somente se o objeto é do mesmo tipo do objeto passado. Ou seja, a propagação não altera o tipo de uma variante com o sinalizador VT_BYREF definido. Se o tipo do objeto for alterado durante a chamada, ocorrerá uma InvalidCastException após o retorno da chamada.

A tabela a seguir resume as regras de propagação para variantes e objetos.

De Para Alterações propagadas novamente
Variantev Objetoo Nunca
Objetoo Variantev Nunca
Variante*pv Objeto de Referênciao Sempre
Objeto de referênciao Variante*pv Sempre
Variantev(VT_BYREF|VT_*) Objetoo Nunca
Variantev(VT_BYREF|VT_) Objeto de Referênciao Somente se o tipo não foi alterado.

Confira também