Invólucro chamável COM
Quando um cliente COM chama um objeto .NET, o common language runtime cria o objeto gerenciado e um wrapper chamável COM (CCW) para o objeto. Não é possível fazer referência a um objeto .NET diretamente, os clientes COM usam o CCW como um proxy para o objeto gerenciado.
O tempo de execução cria exatamente uma CCW para um objeto gerenciado, independentemente do número de clientes COM que solicitam seus serviços. Como mostra a ilustração a seguir, vários clientes COM podem conter uma referência ao CCW que expõe a interface INew. O CCW, por sua vez, contém uma única referência ao objeto gerenciado que implementa a interface e é coletado lixo. Os clientes COM e .NET podem fazer solicitações no mesmo objeto gerenciado simultaneamente.
Os wrappers chamáveis COM são invisíveis para outras classes em execução no tempo de execução do .NET. Seu principal objetivo é organizar chamadas entre código gerenciado e não gerenciado; no entanto, as CCWs também gerenciam a identidade do objeto e o tempo de vida do objeto dos objetos gerenciados que eles encapsulam.
Identidade do objeto
O tempo de execução aloca memória para o objeto .NET de seu heap coletado de lixo, o que permite que o tempo de execução mova o objeto na memória conforme necessário. Em contraste, o tempo de execução aloca memória para o CCW a partir de um heap não coletado, tornando possível para clientes COM fazer referência ao wrapper diretamente.
Tempo de vida do objeto
Ao contrário do cliente .NET que ele encapsula, o CCW é contado como referência no estilo COM tradicional. Quando a contagem de referência no CCW atinge zero, o wrapper libera sua referência no objeto gerenciado. Um objeto gerenciado sem referências restantes é coletado durante o próximo ciclo de coleta de lixo.
Simulando interfaces COM
O CCW expõe todas as interfaces públicas e visíveis por COM, tipos de dados e valores de retorno para clientes COM de uma maneira consistente com a imposição da COM de interação baseada em interface. Para um cliente COM, invocar métodos em um objeto .NET é idêntico a invocar métodos em um objeto COM.
Para criar essa abordagem perfeita, a CCW fabrica interfaces COM tradicionais, como IUnknown e IDispatch. Como mostra a ilustração a seguir, o CCW mantém uma única referência no objeto .NET que ele encapsula. O cliente COM e o objeto .NET interagem entre si por meio da construção de proxy e stub da CCW.
Além de expor as interfaces que são explicitamente implementadas por uma classe no ambiente gerenciado, o tempo de execução do .NET fornece implementações das interfaces COM listadas na tabela a seguir em nome do objeto. Uma classe .NET pode substituir o comportamento padrão fornecendo sua própria implementação dessas interfaces. No entanto, o tempo de execução sempre fornece a implementação para as interfaces IUnknown e IDispatch .
Interface | Description |
---|---|
IDispatch | Fornece um mecanismo para ligação tardia ao tipo. |
IErrorInfo | Fornece uma descrição textual do erro, sua origem, um arquivo de Ajuda, contexto da Ajuda e o GUID da interface que definiu o erro (sempre GUID_NULL para classes .NET). |
IProvideClassInfo | Permite que os clientes COM obtenham acesso à interface ITypeInfo implementada por uma classe gerenciada. Retorna COR_E_NOTSUPPORTED no .NET Core para tipos não importados do COM. |
ISupportErrorInfo | Permite que um cliente COM determine se o objeto gerenciado suporta a interface IErrorInfo . Em caso afirmativo, permite que o cliente obtenha um ponteiro para o objeto de exceção mais recente. Todos os tipos gerenciados suportam a interface IErrorInfo . |
ITypeInfo (somente .NET Framework) | Fornece informações de tipo para uma classe que é exatamente o mesmo que as informações de tipo produzidas por Tlbexp.exe. |
IUnknown | Fornece a implementação padrão da interface IUnknown com a qual o cliente COM gerencia o tempo de vida do CCW e fornece coerção de tipo. |
Uma classe gerenciada também pode fornecer as interfaces COM descritas na tabela a seguir.
Interface | Description |
---|---|
A interface de classe (_classname) | Interface, exposta pelo tempo de execução e não definida explicitamente, que expõe todas as interfaces públicas, métodos, propriedades e campos que são explicitamente expostos em um objeto gerenciado. |
IConnectionPoint e IConnectionPointContainer | Interface para objetos que originam eventos baseados em delegados (uma interface para registrar assinantes de eventos). |
IDispatchEx (somente .NET Framework) | Interface fornecida pelo tempo de execução se a classe implementa IExpando. A interface IDispatchEx é uma extensão da interface IDispatch que, ao contrário de IDispatch, permite enumeração, adição, exclusão e chamada de membros que diferenciam maiúsculas de minúsculas. |
IEnumVARIANT | Interface para classes de tipo de coleção, que enumera os objetos na coleção se a classe implementa IEnumerable. |
Apresentando a interface de classe
A interface de classe, que não é explicitamente definida no código gerenciado, é uma interface que expõe todos os métodos públicos, propriedades, campos e eventos que são explicitamente expostos no objeto .NET. Esta interface pode ser uma interface dupla ou apenas de expedição. A interface de classe recebe o nome da própria classe .NET, precedida por um sublinhado. Por exemplo, para a classe Mammal, a interface de classe é _Mammal.
Para classes derivadas, a interface de classe também expõe todos os métodos públicos, propriedades e campos da classe base. A classe derivada também expõe uma interface de classe para cada classe base. Por exemplo, se a classe Mammal estende a classe MammalSuperclass, que por si só estende System.Object, o objeto .NET expõe aos clientes COM três interfaces de classe chamadas _Mammal, _MammalSuperclass e _Object.
Por exemplo, considere a seguinte classe .NET:
' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
Sub Eat()
Sub Breathe()
Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
public void Eat() {}
public void Breathe() {}
public void Sleep() {}
}
O cliente COM pode obter um ponteiro para uma interface de classe chamada _Mammal
. No .NET Framework, você pode usar a ferramenta Type Library Exporter (Tlbexp.exe) para gerar uma biblioteca de tipos contendo a definição de _Mammal
interface. O Exportador de Biblioteca de Tipos não é suportado no .NET Core. Se a Mammal
classe implementasse uma ou mais interfaces, as interfaces apareceriam sob a coclasse.
[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x6002000d)] HRESULT Eat();
[id(0x6002000e)] HRESULT Breathe();
[id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
[default] interface _Mammal;
}
A geração da interface de classe é opcional. Por padrão, a interoperabilidade COM gera uma interface somente de despacho para cada classe exportada para uma biblioteca de tipos. Você pode impedir ou modificar a criação automática dessa interface aplicando o ClassInterfaceAttribute à sua classe. Embora a interface de classe possa facilitar a tarefa de expor classes gerenciadas para COM, seus usos são limitados.
Atenção
Usar a interface de classe, em vez de definir explicitamente a sua, pode complicar o controle de versão futuro da classe gerenciada. Por favor, leia as seguintes diretrizes antes de usar a interface de classe.
Defina uma interface explícita para os clientes COM usarem em vez de gerar a interface de classe.
Como a interoperabilidade COM gera uma interface de classe automaticamente, as alterações pós-versão na sua classe podem alterar o layout da interface de classe exposta pelo Common Language Runtime. Como os clientes COM normalmente não estão preparados para lidar com alterações no layout de uma interface, eles quebram se você alterar o layout de membro da classe.
Esta diretriz reforça a noção de que as interfaces expostas aos clientes COM devem permanecer imutáveis. Para reduzir o risco de quebrar clientes COM reordenando inadvertidamente o layout da interface, isole todas as alterações na classe do layout da interface definindo explicitamente as interfaces.
Use o ClassInterfaceAttribute para desativar a geração automática da interface de classe e implementar uma interface explícita para a classe, como mostra o fragmento de código a seguir:
<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
Implements IExplicit
Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
int IExplicit.M() { return 0; }
}
O valor ClassInterfaceType.None impede que a interface de classe seja gerada quando os metadados de classe são exportados para uma biblioteca de tipos. No exemplo anterior, os clientes COM podem acessar a LoanApp
classe somente por meio da IExplicit
interface.
Evite armazenar em cache identificadores de despacho (DispIds)
Usar a interface de classe é uma opção aceitável para clientes com script, clientes do Microsoft Visual Basic 6.0 ou qualquer cliente de ligação tardia que não armazene em cache os DispIds dos membros da interface. DispIds identificam membros da interface para habilitar a vinculação tardia.
Para a interface de classe, a geração de DispIds é baseada na posição do membro na interface. Se você alterar a ordem do membro e exportar a classe para uma biblioteca de tipos, alterará os DispIds gerados na interface de classe.
Para evitar quebrar clientes COM de ligação tardia ao usar a interface de classe, aplique o ClassInterfaceAttribute com o valor ClassInterfaceType.AutoDispatch . Esse valor implementa uma interface de classe somente despacho, mas omite a descrição da interface da biblioteca de tipos. Sem uma descrição da interface, os clientes não conseguem armazenar em cache DispIds em tempo de compilação. Embora este seja o tipo de interface padrão para a interface de classe, você pode aplicar o valor do atributo explicitamente.
<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
Implements IAnother
Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
public int M() { return 0; }
}
Para obter o DispId de um membro da interface em tempo de execução, os clientes COM podem chamar IDispatch.GetIdsOfNames. Para invocar um método na interface, passe o DispId retornado como um argumento para IDispatch.Invoke.
Restrinja usando a opção de interface dupla para a interface de classe.
As interfaces duplas permitem a ligação antecipada e tardia aos membros da interface por clientes COM. Em tempo de design e durante o teste, você pode achar útil definir a interface de classe como dual. Para uma classe gerenciada (e suas classes base) que nunca será modificada, essa opção também é aceitável. Em todos os outros casos, evite definir a interface de classe como dual.
Uma interface dupla gerada automaticamente pode ser apropriada em casos raros; no entanto, na maioria das vezes, cria complexidade relacionada à versão. Por exemplo, os clientes COM que usam a interface de classe de uma classe derivada podem facilmente quebrar com alterações na classe base. Quando um terceiro fornece a classe base, o layout da interface de classe está fora do seu controle. Além disso, ao contrário de uma interface somente despacho, uma interface dupla (ClassInterfaceType.AutoDual) fornece uma descrição da interface de classe na biblioteca de tipos exportados. Essa descrição incentiva os clientes atrasados a armazenar em cache DispIds em tempo de compilação.
Certifique-se de que todas as notificações de eventos COM estão vinculadas tardiamente.
Por padrão, as informações de tipo COM são incorporadas diretamente em assemblies gerenciados, o que elimina a necessidade de assemblies de interoperabilidade primários (PIAs). No entanto, uma das limitações das informações de tipo incorporadas é que elas não suportam a entrega de notificações de eventos COM por chamadas vtable de ligação antecipada, mas apenas chamadas de ligação IDispatch::Invoke
tardia.
Se seu aplicativo exigir chamadas de associação antecipada para métodos de interface de evento COM, você pode definir a propriedade Embed Interop Types no Visual Studio como true
, ou incluir o seguinte elemento em seu arquivo de projeto:
<EmbedInteropTypes>True</EmbedInteropTypes>