Compartilhar via


Gerador de origens para o ComWrappers

O .NET 8 apresenta um gerador de origens que cria uma implementação da API ComWrappers para você. O gerador reconhece o GeneratedComInterfaceAttribute.

O sistema de interoperabilidade COM interno do runtime do .NET (não gerado pela origem), somente para Windows, gera um stub de IL, um fluxo de instruções de IL que é JIT, em runtime para facilitar a transição do código gerenciado para COM e vice-versa. Como esse stub de integridade é gerado em runtime, ele é incompatível com NativeAOT e Corte de IL. A geração de stub em tempo de execução também pode dificultar o diagnóstico de problemas de marshalling.

A interoperabilidade interna utiliza atributos como ComImport ou DllImport, que dependem da geração de código em tempo de execução. O código a seguir mostra um exemplo disso:

[ComImport]
interface IFoo
{
    void Method(int i);
}

[DllImport("MyComObjectProvider")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);

A API ComWrappers permite a interação com o COM em C# sem usar o sistema COM interno, mas exige um código não seguro substancial e gravado à mão. O gerador de interface COM automatiza esse processo e torna o ComWrappers tão fácil quanto o COM interno, mas o entrega de uma maneira ajustável e amigável ao AOT.

Uso básico

Para utilizar o gerador de interface COM, adicione os atributos GeneratedComInterfaceAttribute e GuidAttribute na definição da interface que você deseja importar ou expor ao COM. O tipo deve ser marcado como partial e ter internal ou public de visibilidade para que o código gerado possa acessá-lo.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
    void Method(int i);
}

Então, para expor uma classe que implementa uma interface para COM, adicione GeneratedComClassAttribute à classe que está implementando. Essa classe também deve ser partial e internal ou public.

[GeneratedComClass]
internal partial class Foo : IFoo
{
    public void Method(int i)
    {
        // Do things
    }
}

Em tempo de compilação, o gerador cria uma implementação da API ComWrappers, e você pode utilizar o tipo StrategyBasedComWrappers ou um tipo derivado personalizado para consumir ou expor a interface COM.

[LibraryImport("MyComObjectProvider")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo, CreateComInterfaceFlags.None);
GivePointerToComInterface(ptr);

Personalizar o marshalling

O gerador de interface COM respeita o atributo MarshalUsingAttribute e alguns usos do atributo MarshalAsAttribute para personalizar o marshalling de parâmetros. Para obter mais informações, consulte como personalizar o marshalling gerado pela origem com o atributo MarshalUsing e personalizar o marshalling de parâmetros com o atributo MarshalAs. As propriedades GeneratedComInterfaceAttribute.StringMarshalling e GeneratedComInterfaceAttribute.StringMarshallingCustomType se aplicam a todos os parâmetros e tipos de retorno do tipo string na interface, se eles não tiverem outros atributos de marshalling.

HRESULTs implícitos e PreserveSig

Os métodos COM em C# têm uma assinatura diferente da dos métodos nativos. O COM Standard tem um tipo de retorno HRESULT, um tipo inteiro de 4 bytes que representa os estados de erro e êxito. Esse valor de retorno HRESULT fica oculto por padrão na assinatura C# e é convertido em uma exceção quando um valor de erro é retornado. O último parâmetro "out" da assinatura COM nativa pode, opcionalmente, ser convertido no retorno da assinatura C#.

Por exemplo, os trechos a seguir mostram assinaturas de métodos C# e a assinatura nativa correspondente que o gerador infere.

void Method1(int i);

int Method2(float i);
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);

Se quiser lidar com o HRESULT por conta própria, você pode utilizar o PreserveSigAttribute no método para indicar que o gerador não deve fazer essa transformação. Os trechos a seguir demonstram a assinatura nativa que o gerador espera quando [PreserveSig] é aplicado. Os métodos COM devem retornar HRESULT, portanto, o valor de retorno de qualquer método com PreserveSig deverá ser int.

[PreserveSig]
int Method1(int i, out int j);

[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);

Para obter mais informações, consulte Traduções da assinatura de método implícitas na interoperabilidade do .NET

Incompatibilidades e diferenças em relação à COM interna

IUnknown somente

A única base de interface com suporte é IUnknown. Interfaces com InterfaceTypeAttribute que tenham um valor diferente de InterfaceIsIUnknown não é suportado na COM gerada pela origem. Presume-se que todas as interfaces sem InterfaceTypeAttribute sejam derivadas de IUnknown. Isso difere do COM interno, em que o padrão é InterfaceIsDual.

Padrões e suporte de Marshalling

O COM gerado pela origem tem alguns comportamentos de marshalling padrão diferentes do COM interno.

  • No sistema COM interno, todos os tipos têm um atributo [In] implícito, exceto as matrizes de elementos blittable, que têm atributos [In, Out] implícitos. Na COM gerada pela origem, todos os tipos, incluindo matrizes de elementos blittable, têm a semântica [In].

  • Os atributos [In] e [Out] só são permitidos em matrizes. Se o comportamento [Out] ou [In, Out] for obrigatório em outros tipos, use os modificadores de parâmetro in e out.

Interfaces derivadas

No sistema COM interno, se você tiver interfaces que derivam de outras interfaces COM, deverá declarar um método de sombreamento para cada método de base nas interfaces de base com a palavra-chave new. Para obter mais informações, consulte Herança da interface COM e .NET.

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    new void Method1(int i);
    new void Method2(float f);
    void Method3(long l);
    void Method4(double d);
}

O gerador de interface COM não espera nenhum sombreamento de métodos básicos. Para criar um método que herda de outro, basta indicar a interface de base como uma interface de base do C# e adicionar os métodos da interface derivada. Para obter mais informações, consulte o documento de design.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    void Method3(long l);
    void Method4(double d);
}

Observe que uma interface com o atributo GeneratedComInterface só pode herdar de uma interface de base que tenha o atributo GeneratedComInterface.

Interfaces derivadas através dos limites da montagem

No .NET 8, não há suporte para definir uma interface com o atributo GeneratedComInterfaceAttribute que deriva de uma interface atribuída GeneratedComInterface definida em outro assembly.

No .NET 9 e versões posteriores, esse cenário tem suporte com as seguintes restrições:

  • O tipo de interface base deve ser compilado visando a mesma estrutura de destino do tipo derivado.
  • O tipo de interface base não deve ocultar nenhum membro de sua interface base, se houver.

Além disso, quaisquer alterações em quaisquer deslocamentos de método virtual gerados na cadeia de interface base definida em outro assembly não serão contabilizadas nas interfaces derivadas até que o projeto seja reconstruído.

Observação

No .NET 9 e versões posteriores, um aviso é emitido ao herdar interfaces COM geradas através dos limites do assembly para informar sobre as restrições e armadilhas do uso desse recurso. Você pode desabilitar esse aviso para reconhecer as limitações e herdar os limites da montagem.

APIs Marshal

Algumas APIs em Marshal não são compatíveis com a COM gerada pela origem. Substitua esses métodos por seus métodos correspondentes em uma implementação ComWrappers.

Confira também