Qualificar tipos .NET para interoperação COM
Expor tipos .NET a COM
Se você pretende expor tipos em uma montagem para aplicativos COM, considere os requisitos de interoperabilidade COM em tempo de design. Os tipos gerenciados (classe, interface, estrutura e enumeração) integram-se perfeitamente aos tipos COM quando você adere às seguintes diretrizes:
As classes devem implementar interfaces explicitamente.
Embora a interoperabilidade COM forneça um mecanismo para gerar automaticamente uma interface contendo todos os membros da classe e os membros de sua classe base, é muito melhor fornecer interfaces explícitas. A interface gerada automaticamente é chamada de interface de classe. Para obter diretrizes, consulte Apresentando a interface de classe.
Você pode usar Visual Basic, C# e C++ para incorporar definições de interface em seu código, em vez de ter que usar Interface Definition Language (IDL) ou seu equivalente. Para obter detalhes de sintaxe, consulte a documentação do idioma.
Os tipos gerenciados devem ser públicos.
Somente os tipos públicos em um assembly são registrados e exportados para a biblioteca de tipos. Como resultado, apenas os tipos públicos são visíveis para COM.
Os tipos gerenciados expõem recursos a outros códigos gerenciados que podem não estar expostos ao COM. Por exemplo, construtores parametrizados, métodos estáticos e campos constantes não são expostos a clientes COM. Além disso, à medida que o tempo de execução controla os dados dentro e fora de um tipo, os dados podem ser copiados ou transformados.
Métodos, propriedades, campos e eventos devem ser públicos.
Os membros de tipos públicos também devem ser públicos para serem visíveis para a COM. Você pode restringir a visibilidade de um assembly, um tipo público ou membros públicos de um tipo público aplicando o ComVisibleAttribute. Por padrão, todos os tipos e membros públicos são visíveis.
Os tipos devem ter um construtor sem parâmetros público para serem ativados a partir de COM.
Os tipos públicos gerenciados são visíveis para COM. No entanto, sem um construtor sem parâmetros público (um construtor sem argumentos), os clientes COM não podem criar o tipo. Os clientes COM ainda podem usar o tipo se ele for ativado por algum outro meio.
Os tipos não podem ser abstratos.
Nem os clientes COM nem os clientes .NET podem criar tipos abstratos.
Quando exportada para COM, a hierarquia de herança de um tipo gerenciado é nivelada. O controle de versão também difere entre ambientes gerenciados e não gerenciados. Os tipos expostos a COM não têm as mesmas características de controle de versão que outros tipos gerenciados.
Consumir tipos COM do .NET
Se você pretende consumir tipos COM do .NET e não deseja usar ferramentas como Tlbimp.exe (Type Library Importer), você deve seguir estas diretrizes:
- As interfaces devem ter o ComImportAttribute aplicado.
- As interfaces devem ter o GuidAttribute aplicado com o ID de interface para a interface COM.
- As interfaces devem ter o InterfaceTypeAttribute aplicado para especificar o tipo de interface base desta interface (
IUnknown
,IDispatch
, ouIInspectable
).- A opção padrão é ter o tipo base e anexar os métodos declarados à tabela de
IDispatch
funções virtuais esperada para a interface. - Somente o .NET Framework oferece suporte à especificação de um tipo base de
IInspectable
.
- A opção padrão é ter o tipo base e anexar os métodos declarados à tabela de
Estas orientações fornecem os requisitos mínimos para cenários comuns. Existem muitas outras opções de personalização que são descritas em Aplicando atributos de interoperabilidade.
Definir interfaces COM no .NET
Quando o código .NET tenta chamar um método em um objeto COM por meio de uma interface com o ComImportAttribute atributo, ele precisa criar uma tabela de funções virtuais (também conhecida como vtable ou vftable) para formar a definição .NET da interface para determinar o código nativo a ser chamado. Este processo é complexo. Os exemplos a seguir mostram alguns casos simples.
Considere uma interface COM com alguns métodos:
struct IComInterface : public IUnknown
{
STDMETHOD(Method)() = 0;
STDMETHOD(Method2)() = 0;
};
Para essa interface, a tabela a seguir descreve seu layout de tabela de função virtual:
IComInterface slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
Cada método é adicionado à tabela de funções virtuais na ordem em que foi declarado. A ordem particular é definida pelo compilador C++, mas para casos simples sem sobrecargas, a ordem de declaração define a ordem na tabela.
Declare uma interface .NET que corresponde a essa interface da seguinte maneira:
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(/* The IID for IComInterface */)]
interface IComInterface
{
void Method();
void Method2();
}
O InterfaceTypeAttribute especifica a interface base. Ele fornece algumas opções:
ComInterfaceType Valor | Tipo de interface base | Comportamento para membros na interface atribuída |
---|---|---|
InterfaceIsIUnknown |
IUnknown |
A tabela de funções virtuais primeiro tem os membros do , depois os membros desta interface em ordem de IUnknown declaração. |
InterfaceIsIDispatch |
IDispatch |
Os membros não são adicionados à tabela de funções virtuais. Eles só são acessíveis através IDispatch do . |
InterfaceIsDual |
IDispatch |
A tabela de funções virtuais primeiro tem os membros do , depois os membros desta interface em ordem de IDispatch declaração. |
InterfaceIsIInspectable |
IInspectable |
A tabela de funções virtuais primeiro tem os membros do , depois os membros desta interface em ordem de IInspectable declaração. Apenas suportado no .NET Framework. |
Herança da interface COM e .NET
O sistema de interoperabilidade COM que usa o não interage com a ComImportAttribute herança da interface, portanto, pode causar um comportamento inesperado, a menos que algumas etapas atenuantes sejam tomadas.
O gerador de origem COM que usa o atributo interage com a System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute
herança da interface, portanto, ele se comporta mais conforme o esperado.
Herança da interface COM em C++
Em C++, os desenvolvedores podem declarar interfaces COM que derivam de outras interfaces COM da seguinte maneira:
struct IComInterface : public IUnknown
{
STDMETHOD(Method)() = 0;
STDMETHOD(Method2)() = 0;
};
struct IComInterface2 : public IComInterface
{
STDMETHOD(Method3)() = 0;
};
Esse estilo de declaração é usado regularmente como um mecanismo para adicionar métodos a objetos COM sem alterar as interfaces existentes, o que seria uma mudança de rutura. Esse mecanismo de herança resulta nos seguintes layouts de tabela de função virtual:
IComInterface slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
IComInterface2 slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
5 | IComInterface2::Method3 |
Como resultado, é fácil chamar um método definido a IComInterface
partir de um IComInterface2*
arquivo . Especificamente, chamar um método em uma interface base não requer uma chamada para QueryInterface
obter um ponteiro para a interface base. Além disso, o C++ permite uma conversão implícita de IComInterface2*
para IComInterface*
, que é bem definida e permite que você evite chamar um QueryInterface
novamente. Como resultado, em C ou C++, você nunca precisa ligar QueryInterface
para chegar ao tipo base se não quiser, o que pode permitir algumas melhorias de desempenho.
Nota
As interfaces do WinRT não seguem esse modelo de herança. Eles são definidos para seguir o mesmo modelo [ComImport]
do modelo de interoperabilidade COM baseado em .NET.
Herança de interface com ComImportAttribute
No .NET, o código C# que se parece com herança de interface não é, na verdade, herança de interface. Considere o seguinte código:
interface I
{
void Method1();
}
interface J : I
{
void Method2();
}
Este código não diz, "J
implementa I
". O código, na verdade, diz: "qualquer tipo que implemente J
também deve implementar I
". Essa diferença leva à decisão fundamental de design que torna a herança de interface na interoperabilidade baseada não ComImportAttributeergonômica. As interfaces são sempre consideradas por conta própria; a lista de interfaces base de uma interface não tem impacto em nenhum cálculo para determinar uma tabela de funções virtuais para uma determinada interface .NET.
Como resultado, o equivalente natural do exemplo de interface COM C++ anterior leva a um layout de tabela de função virtual diferente.
Código C#:
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
void Method();
void Method2();
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
void Method3();
}
Layouts de tabelas de funções virtuais:
IComInterface slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
IComInterface2 slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface2::Method3 |
Como essas tabelas de funções virtuais diferem do exemplo C++, isso levará a sérios problemas em tempo de execução. A definição correta dessas interfaces no .NET com ComImportAttribute é a seguinte:
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
void Method();
void Method2();
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
new void Method();
new void Method2();
void Method3();
}
No nível de metadados, IComInterface2
não implementa IComInterface
, mas apenas especifica que os implementadores de IComInterface2
também devem implementar IComInterface
. Assim, cada método dos tipos de interface base deve ser reformulado.
Herança de interface com GeneratedComInterfaceAttribute
(.NET 8 e posterior)
O gerador de código-fonte COM acionado pelo implementa a GeneratedComInterfaceAttribute
herança da interface C# como herança da interface COM, para que as tabelas de funções virtuais sejam dispostas conforme o esperado. Se você pegar o exemplo anterior, a definição correta dessas interfaces no .NET com System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute
é a seguinte:
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
void Method();
void Method2();
}
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
void Method3();
}
Os métodos das interfaces de base não precisam ser redeclarados e não devem ser redeclarados. A tabela a seguir descreve as tabelas de funções virtuais resultantes:
IComInterface slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
IComInterface2 slot de tabela de função virtual |
Nome do método |
---|---|
0 | IUnknown::QueryInterface |
1 | IUnknown::AddRef |
2 | IUnknown::Release |
3 | IComInterface::Method |
4 | IComInterface::Method2 |
5 | IComInterface2::Method3 |
Como você pode ver, essas tabelas correspondem ao exemplo C++, portanto, essas interfaces funcionarão corretamente.