Compartilhar via


Interface System.Runtime.InteropServices.ICustomMarshaler

Este artigo fornece comentários complementares à documentação de referência para esta API.

A ICustomMarshaler interface fornece envoltórios personalizados para a manipulação de chamadas de método.

Um marshaller fornece uma ponte entre a funcionalidade de interfaces antigas e novas. A marshalização personalizada fornece os seguintes benefícios:

  • Ele permite que os aplicativos cliente que foram projetados para trabalhar com uma interface antiga também funcionem com servidores que implementam uma nova interface.
  • Ele permite que os aplicativos cliente criados para trabalhar com uma nova interface funcionem com servidores que implementam uma interface antiga.

Se você tiver uma interface que introduz um comportamento de marshaling diferente ou que seja exposto ao COM (Component Object Model) de uma maneira diferente, você poderá projetar um marshaller personalizado em vez de usar o marshaller de interoperabilidade. Usando um marshaller personalizado, você pode minimizar a distinção entre os novos componentes do .NET Framework e os componentes COM existentes.

Por exemplo, suponha que você esteja desenvolvendo uma interface gerenciada chamada INew. Quando essa interface é exposta ao COM por meio de um "COM callable wrapper" (CCW) padrão, ela mantém os mesmos métodos que a interface gerenciada e utiliza as regras de marshaling integradas ao marshaller de interoperabilidade. Agora suponha que uma interface COM conhecida chamada IOld já forneça a mesma funcionalidade que a INew interface. Ao criar um marshaller personalizado, você pode implementar uma versão não-gerenciada de IOld, que simplesmente delega as chamadas à implementação gerenciada da interface INew. Portanto, o marshaller personalizado atua como uma ponte entre as interfaces gerenciadas e não gerenciadas.

Observação

Os marshallers personalizados não são invocados ao chamar do código gerenciado para o código não gerenciado em uma interface somente de despacho.

Definir o tipo marshaling

Antes de criar um marshaller personalizado, é necessário definir as interfaces gerenciadas e não gerenciadas que devem realizar marshaling. Essas interfaces geralmente executam a mesma função, mas são expostas de forma diferente a objetos gerenciados e não gerenciados.

Um compilador gerenciado produz uma interface gerenciada de metadados e a interface resultante se parece com qualquer outra interface gerenciada. O exemplo a seguir mostra uma interface típica.

public interface INew
{
    void NewMethod();
}
Public Interface INew
    Sub NewMethod()
End Interface

Defina o tipo não gerenciado na IDL (Interface Definition Language) e compile-o com o compilador MIDL (Microsoft Interface Definition Language). Defina a interface dentro de uma instrução de biblioteca e atribua a ela uma ID de interface com o atributo UUID (identificador exclusivo universal), como demonstra o exemplo a seguir.

 [uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library OldLib {
     [uuid(9B2BAADD-0705-11D3-A0CD-00C04FA35826)]
     interface IOld : IUnknown
         HRESULT OldMethod();
}

O compilador MIDL produz vários arquivos de saída. Se a interface for definida em Old.idl, o arquivo de saída Old_i.c definirá uma const variável com o IID (identificador de interface) da interface, como demonstra o exemplo a seguir.

const IID IID_IOld = {0x9B2BAADD,0x0705,0x11D3,{0xA0,0xCD,0x00,0xC0,0x4F,0xA3,0x58,0x26}};

O arquivo Old.h também é produzido por MIDL. Ele contém uma definição C++ da interface que pode ser incluída no código-fonte do C++.

Implementar a interface ICustomMarshaler

Seu marshaller personalizado deve implementar a interface ICustomMarshaler para fornecer os wrappers apropriados ao runtime.

O código C# a seguir exibe a interface base que deve ser implementada por todos os marshallers personalizados.

public interface ICustomMarshaler
{
    Object MarshalNativeToManaged(IntPtr pNativeData);
    IntPtr MarshalManagedToNative(Object ManagedObj);
    void CleanUpNativeData(IntPtr pNativeData);
    void CleanUpManagedData(Object ManagedObj);
    int GetNativeDataSize();
}
Public Interface ICustomMarshaler
     Function MarshalNativeToManaged( pNativeData As IntPtr ) As Object
     Function MarshalManagedToNative( ManagedObj As Object ) As IntPtr
     Sub CleanUpNativeData( pNativeData As IntPtr )
     Sub CleanUpManagedData( ManagedObj As Object )
     Function GetNativeDataSize() As Integer
End Interface

A interface ICustomMarshaler inclui métodos que oferecem suporte à conversão, suporte à limpeza e informações sobre os dados que devem realizar marshaling.

Tipo de operação Método ICustomMarshaler Descrição
Conversão (de código nativo para gerenciado) MarshalNativeToManaged Converte um ponteiro para dados nativos em um objeto gerenciado. Esse método retorna um RCW (runtime callable wrapper) que pode fazer o marshaling da interface não gerenciada que é passada como um argumento. O marshaller deve retornar uma instância do RCW personalizado para esse tipo.
Conversão (de código gerenciado para nativo) MarshalManagedToNative Converte um objeto gerenciado em um ponteiro para dados em formato nativo. Esse método retorna um CCW (COM callable wrapper) personalizado que pode realizar marshaling na interface gerenciada que é passada como argumento. O marshaller deve retornar uma instância do CCW personalizado para esse tipo.
Limpeza (do código nativo) CleanUpNativeData Permite que o marshaller limpe os dados nativos (o CCW) que são retornados pelo método MarshalManagedToNative.
Limpeza (do código gerenciado) CleanUpManagedData Permite que o marshaller limpe os dados gerenciados (o RCW) que são retornados pelo método MarshalNativeToManaged.
Informações (sobre código nativo) GetNativeDataSize Retorna o tamanho dos dados não gerenciados nos quais deve-se realizar marshaling.

Conversão

ICustomMarshaler.MarshalNativeToManaged

Converte um ponteiro para dados nativos em um objeto gerenciado. Esse método retorna um RCW (runtime callable wrapper) que pode fazer o marshaling da interface não gerenciada que é passada como um argumento. O marshaller deve retornar uma instância do RCW personalizado para esse tipo.

ICustomMarshaler.MarshalManagedToNative

Converte um objeto gerenciado em um ponteiro para dados em formato nativo. Esse método retorna um CCW (COM callable wrapper) personalizado que pode realizar marshaling na interface gerenciada que é passada como argumento. O marshaller deve retornar uma instância do CCW personalizado para esse tipo.

Limpeza

ICustomMarshaler.CleanUpNativeData

Permite que o marshaller limpe os dados nativos (o CCW) que são retornados pelo método MarshalManagedToNative.

ICustomMarshaler.CleanUpManagedData

Permite que o marshaller limpe os dados gerenciados (o RCW) que são retornados pelo método MarshalNativeToManaged.

Informações de tamanho

ICustomMarshaler.GetNativeDataSize

Retorna o tamanho dos dados não gerenciados nos quais deve-se realizar marshaling.

Observação

Se um marshaller personalizado chamar qualquer método que defina o último erro P/Invoke durante a conversão de nativo para gerenciado ou durante a limpeza, o valor retornado pelos Marshal.GetLastWin32Error() e Marshal.GetLastPInvokeError() representará a chamada nas operações de conversão ou limpeza. Isso pode fazer com que erros sejam ignorados ao usar marshallers personalizados com P/Invokes com DllImportAttribute.SetLastError definido como true. Para preservar o último erro P/Invoke, use os métodos Marshal.GetLastPInvokeError() e Marshal.SetLastPInvokeError(Int32) na implementação ICustomMarshaler.

Implementar o método GetInstance

Além de implementar a interface ICustomMarshaler, os marshallers personalizados devem implementar um método static chamado GetInstance que aceita um String como parâmetro e tem um tipo de retorno ICustomMarshaler. Esse static método é chamado pela camada de interoperabilidade COM do Common Language Runtime para criar uma instância do marshaller personalizado. A cadeia de caracteres que é passada GetInstance é um cookie que o método pode usar para personalizar o marshaller personalizado retornado. O exemplo a seguir mostra uma implementação mínima, mas completa ICustomMarshaler .

public class NewOldMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string pstrCookie)
        => new NewOldMarshaler();

    public Object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException();
    public IntPtr MarshalManagedToNative(Object ManagedObj) => throw new NotImplementedException();
    public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
    public void CleanUpManagedData(Object ManagedObj) => throw new NotImplementedException();
    public int GetNativeDataSize() => throw new NotImplementedException();
}

Aplicar o atributo MarshalAsAttribute

Para usar um marshaller personalizado, você deve aplicar o atributo MarshalAsAttribute ao parâmetro ou campo que está sendo processado.

Também é necessário passar o UnmanagedType.CustomMarshaler valor de enumeração para o MarshalAsAttribute construtor. Além disso, você deve especificar o MarshalType campo com um dos seguintes parâmetros nomeados:

  • MarshalType (obrigatório): o nome qualificado de montagem do marshaller personalizado. O nome deve incluir o namespace e a classe do marshaller personalizado. Se o marshaller personalizado não estiver definido no assembly em que é usado, será necessário especificar o nome do assembly onde ele está definido.

    Observação

    Você pode usar o MarshalTypeRef campo em vez do MarshalType campo. MarshalTypeRef usa um tipo que é mais fácil de especificar.

  • MarshalCookie (opcional): um cookie que é passado para o marshaller personalizado. É possível usar o cookie para fornecer informações adicionais ao marshaller. Por exemplo, se o mesmo marshaller for usado para fornecer vários wrappers, o cookie identificará um wrapper específico. O cookie é passado para o método GetInstance do marshaller.

O atributo MarshalAsAttribute identifica o marshaller personalizado para que ele possa ativar o wrapper apropriado. O serviço de interoperabilidade do Common Language Runtime examina o atributo e cria o marshaller personalizado na primeira vez que o argumento (parâmetro ou campo) precisa ser empacotado.

Em seguida, o runtime chama os métodos MarshalNativeToManaged e MarshalManagedToNative no marshaller personalizado para ativar o wrapper correto para lidar com a chamada.

Usar um marshaller personalizado

Quando o marshaller personalizado for concluído, será possível usá-lo como um wrapper personalizado para um tipo específico. O exemplo a seguir mostra a definição da IUserData interface gerenciada:

interface IUserData
{
    void DoSomeStuff(INew pINew);
}
Public Interface IUserData
    Sub DoSomeStuff(pINew As INew)
End Interface

No exemplo a seguir, a interface IUserData utiliza o marshaller personalizado NewOldMarshaler para permitir que aplicativos cliente não gerenciados passem uma interface IOld ao método DoSomeStuff. A descrição gerenciada do método DoSomeStuff usa uma interface INew, conforme mostrado no exemplo anterior, enquanto a versão não gerenciada do DoSomeStuff toma um ponteiro de interface IOld, conforme mostrado no exemplo a seguir.

[uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library UserLib {
     [uuid(9B2BABCD-0705-11D3-A0CD-00C04FA35826)]
     interface IUserData : IUnknown
         HRESULT DoSomeStuff(IUnknown* pIOld);
}

A biblioteca de tipos que é gerada pela exportação da definição gerenciada de IUserData resulta na definição não gerenciada mostrada neste exemplo, em vez da definição padrão. O MarshalAsAttribute atributo aplicado ao INew argumento na definição gerenciada do DoSomeStuff método indica que o argumento usa um marshaller personalizado, como mostra o exemplo a seguir.

using System.Runtime.InteropServices;
Imports System.Runtime.InteropServices
interface IUserData
{
    void DoSomeStuff(
        [MarshalAs(UnmanagedType.CustomMarshaler,
         MarshalType="NewOldMarshaler")]
    INew pINew
    );
}
Public Interface IUserData
    Sub DoSomeStuff( _
        <MarshalAs(UnmanagedType.CustomMarshaler, _
        MarshalType := "MyCompany.NewOldMarshaler")> pINew As INew)
End Interface

Nos exemplos anteriores, o primeiro parâmetro fornecido ao atributo MarshalAsAttribute é o valor de enumeração UnmanagedType.CustomMarshalerUnmanagedType.CustomMarshaler.

O segundo parâmetro é o campo MarshalType, que fornece o nome qualificado do assembly do marshaller personalizado. Esse nome consiste no namespace e na classe do marshaller personalizado (MarshalType="MyCompany.NewOldMarshaler").