Interface System.Runtime.InteropServices.ICustomMarshaler

Este artigo fornece observações complementares à documentação de referência para essa API.

A ICustomMarshaler interface fornece wrappers personalizados para lidar com chamadas de método.

Um marshaller fornece uma ponte entre a funcionalidade de interfaces antigas e novas. O empacotamento personalizado oferece os seguintes benefícios:

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

Se você tiver uma interface que introduza um comportamento de empacotamento diferente ou que seja exposta ao COM (Component Object Model) de uma maneira diferente, você pode criar um marshaller personalizado em vez de usar o marshaller de interoperabilidade. Usando um marshaller personalizado, você pode minimizar a distinção entre novos componentes do .NET Framework e 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 wrapper chamável COM padrão (CCW), ela tem os mesmos métodos que a interface gerenciada e usa as regras de empacotamento embutidas no marshaller de interoperabilidade. Agora suponha que uma interface COM bem conhecida chamada IOld já fornece a mesma funcionalidade que a INew interface. Ao projetar um marshaller personalizado, você pode fornecer uma implementação não gerenciada que IOld simplesmente delega as chamadas à implementação gerenciada da INew interface. 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 de código gerenciado para código não gerenciado em uma interface somente de expedição.

Definir o tipo de marshaling

Antes de criar um marshaller personalizado, você deve definir as interfaces gerenciadas e não gerenciadas que serão empacotadas. 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 a partir 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

Definir o tipo não gerenciado em IDL (Interface Definition Language) e compilá-lo com o compilador Microsoft Interface Definition Language (MIDL). Você define a interface dentro de uma instrução de biblioteca e atribui a ela uma ID de interface com o atributo de identificador exclusivo universal (UUID), 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 identificador de interface (IID) 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 pelo MIDL. Ele contém uma definição C++ da interface que pode ser incluída no código-fonte C++.

Implementar a interface ICustomMarshaler

Seu marshaller personalizado deve implementar a ICustomMarshaler interface para fornecer os wrappers apropriados para o tempo de execução.

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 ICustomMarshaler interface inclui métodos que fornecem suporte à conversão, suporte à limpeza e informações sobre os dados a serem empacotados.

Tipo de operação Método ICustomMarshaler Descrição
Conversão (de código nativo para gerenciado) MarshalNativeToManaged Marshals um ponteiro para dados nativos em um objeto gerenciado. Esse método retorna um wrapper chamável de tempo de execução personalizado (RCW) que pode marshal a 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 Marshals um objeto gerenciado em um ponteiro para dados nativos. Esse método retorna um wrapper chamável COM personalizado (CCW) que pode marshal a interface gerenciada que é passada como um argumento. O marshaller deve retornar uma instância do CCW personalizado para esse tipo.
Limpeza (de código nativo) CleanUpNativeData Permite que o marshaller limpe os dados nativos (o CCW) retornados pelo MarshalManagedToNative método.
Limpeza (de código gerenciado) CleanUpManagedData Permite que o marshaller limpe os dados gerenciados (o RCW) retornados pelo MarshalNativeToManaged método.
Informações (sobre código nativo) GetNativeDataSize Retorna o tamanho dos dados não gerenciados a serem empacotados.

Conversão

ICustomMarshaler.MarshalNativeToManaged

Marshals um ponteiro para dados nativos em um objeto gerenciado. Esse método retorna um wrapper chamável de tempo de execução personalizado (RCW) que pode marshal a interface não gerenciada que é passada como um argumento. O marshaller deve retornar uma instância do RCW personalizado para esse tipo.

ICustomMarshaler.MarshalManagedToNative

Marshals um objeto gerenciado em um ponteiro para dados nativos. Esse método retorna um wrapper chamável COM personalizado (CCW) que pode marshal a interface gerenciada que é passada como um 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) retornados pelo MarshalManagedToNative método.

ICustomMarshaler.CleanUpManagedData

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

Informações de tamanho

ICustomMarshaler.GetNativeDataSize

Retorna o tamanho dos dados não gerenciados a serem empacotados.

Observação

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

Implementar o método GetInstance

Além de implementar a interface, os marshallers personalizados devem implementar um método chamado GetInstance que aceita a StringICustomMarshaler como um parâmetro e tem um static tipo de retorno de ICustomMarshaler. Esse static método é chamado pela camada de interoperabilidade COM do Common Language Runtime para instanciar uma instância do marshaller personalizado. A cadeia de caracteres que é passada para é um cookie que o método pode usar para GetInstance 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 MarshalAsAttribute

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

Você também deve passar o valor de enumeração para o UnmanagedType.CustomMarshalerMarshalAsAttribute construtor. Além disso, você deve especificar o MarshalType campo com um dos seguintes parâmetros nomeados:

  • MarshalType (obrigatório): O nome qualificado para montagem do marechal 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, você deverá especificar o nome do assembly no qual ele está definido.

    Observação

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

  • MarshalCookie (opcional): um cookie que é passado para o marshaller personalizado. Você pode usar o cookie para fornecer informações adicionais ao marshaller. Por exemplo, se o mesmo marshaller for usado para fornecer um número de invólucros, o cookie identificará um invólucro específico. O cookie é passado para o GetInstance método do marshaller.

O MarshalAsAttribute atributo 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 tempo de execução chama os MarshalNativeToManaged métodos e MarshalManagedToNative no marshaller personalizado para ativar o wrapper correto para manipular a chamada.

Usar um marshaller personalizado

Quando o marshaller personalizado estiver concluído, você poderá 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 IUserData interface usa o marshaller personalizado para permitir que aplicativos cliente não gerenciados passem uma IOld interface para o NewOldMarshalerDoSomeStuff método. A descrição gerenciada do método usa uma INew interface, como mostrado no exemplo anterior, enquanto a versão não gerenciada do DoSomeStuff usa um IOld ponteiro de DoSomeStuff interface, 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 gerada pela exportação da definição gerenciada de produz a definição não gerenciada mostrada IUserData 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 MarshalAsAttribute atributo é o valor UnmanagedType.CustomMarshalerde UnmanagedType.CustomMarshaler enumeração .

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