Partilhar via


Tutorial: Usar a ComWrappers API

Neste tutorial, você aprenderá como subclassificar corretamente o ComWrappers tipo para fornecer uma solução de interoperabilidade COM otimizada e amigável para AOT. Antes de iniciar este tutorial, você deve estar familiarizado com COM, sua arquitetura e soluções de interoperabilidade COM existentes.

Neste tutorial, você implementará as seguintes definições de interface. Essas interfaces e suas implementações demonstrarão:

  • Marshalling e unmarshalling tipos através do limite COM/.NET.
  • Duas abordagens distintas para consumir objetos COM nativos no .NET.
  • Um padrão recomendado para habilitar a interoperabilidade COM personalizada no .NET 5 e posterior.

Todo o código-fonte usado neste tutorial está disponível no repositório dotnet/samples.

Nota

No SDK do .NET 8 e versões posteriores, um gerador de código-fonte é fornecido para gerar automaticamente uma implementação de ComWrappers API para você. Para obter mais informações, consulte ComWrappers Geração de fontes.

Definições de C#

interface IDemoGetType
{
    string? GetString();
}

interface IDemoStoreType
{
    void StoreString(int len, string? str);
}

Definições do Win32 C++

MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
IDemoGetType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE GetString(_Outptr_ wchar_t** str) = 0;
};

MIDL_INTERFACE("30619FEA-E995-41EA-8C8B-9A610D32ADCB")
IDemoStoreType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE StoreString(int len, _In_z_ const wchar_t* str) = 0;
};

Visão geral do ComWrappers design

A ComWrappers API foi projetada para fornecer a interação mínima necessária para realizar a interoperabilidade COM com o tempo de execução do .NET 5+. Isso significa que muitas das sutilezas que existem com o sistema de interoperabilidade COM integrado não estão presentes e devem ser construídas a partir de blocos de construção básicos. As duas principais responsabilidades da API são:

Essas eficiências são alcançadas exigindo que a ComWrappers criação e aquisição de wrapper passem pela API.

Como a ComWrappers API tem tão poucas responsabilidades, é lógico que a maior parte do trabalho de interoperabilidade deve ser tratada pelo consumidor – isso é verdade. No entanto, o trabalho adicional é em grande parte mecânico e pode ser realizado por uma solução de geração de fonte. Como exemplo, a cadeia de ferramentas C#/WinRT é uma solução de geração de ComWrappers origem criada para fornecer suporte de interoperabilidade do WinRT.

Implementar uma ComWrappers subclasse

Fornecer uma ComWrappers subclasse significa fornecer informações suficientes ao tempo de execução do .NET para criar e registrar wrappers para objetos gerenciados que estão sendo projetados em objetos COM e COM que estão sendo projetados no .NET. Antes de olharmos para um esboço da subclasse, devemos definir alguns termos.

Managed Object Wrapper – Os objetos .NET gerenciados exigem wrappers para habilitar o uso de um ambiente non-.NET. Esses wrappers são historicamente chamados de COM Callable Wrappers (CCW).

Native Object Wrapper – Os objetos COM que são implementados em uma linguagem non-.NET exigem wrappers para habilitar o uso do .NET. Esses wrappers são historicamente chamados de Runtime Callable Wrappers (RCW).

Passo 1 – Definir métodos para implementar e compreender a sua intenção

Para estender o ComWrappers tipo, você deve implementar os três métodos a seguir. Cada um desses métodos representa a participação do usuário na criação ou exclusão de um tipo de wrapper. Os ComputeVtables() métodos e CreateObject() criam um Managed Object Wrapper e um Native Object Wrapper, respectivamente. O ReleaseObjects() método é usado pelo tempo de execução para fazer uma solicitação para que a coleção fornecida de wrappers seja "liberada" do objeto nativo subjacente. Na maioria dos casos, o ReleaseObjects() corpo do método pode simplesmente lançarNotImplementedException, uma vez que só é chamado em um cenário avançado envolvendo a estrutura do Reference Tracker.

// See referenced sample for implementation.
class DemoComWrappers : ComWrappers
{
    protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) =>
        throw new NotImplementedException();

    protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags) =>
        throw new NotImplementedException();

    protected override void ReleaseObjects(IEnumerable objects) =>
        throw new NotImplementedException();
}

Para implementar o ComputeVtables() método, decida quais tipos gerenciados você gostaria de suportar. Para este tutorial, suportaremos as duas interfaces definidas anteriormente (IDemoGetType e IDemoStoreType) e um tipo gerenciado que implementa as duas interfaces (DemoImpl).

class DemoImpl : IDemoGetType, IDemoStoreType
{
    string? _string;
    public string? GetString() => _string;
    public void StoreString(int _, string? str) => _string = str;
}

Para o CreateObject() método, você também precisará determinar o que deseja suportar. Neste caso, porém, só conhecemos as interfaces COM em que estamos interessados, não as classes COM. As interfaces que estão sendo consumidas do lado COM são as mesmas que estamos projetando do lado .NET (ou seja, IDemoGetType e IDemoStoreType).

Não implementaremos ReleaseObjects() neste tutorial.

Etapa 2 – Implementar ComputeVtables()

Vamos começar com o Managed Object Wrapper – esses wrappers são mais fáceis. Você criará uma Virtual Method Table, ou vtable, para cada interface, a fim de projetá-los no ambiente COM. Para este tutorial, você definirá um vtable como uma sequência de ponteiros, onde cada ponteiro representa uma implementação de uma função em uma interface – a ordem é muito importante aqui. Em COM, cada interface herda do IUnknown. O IUnknown tipo tem três métodos definidos na seguinte ordem: QueryInterface(), AddRef()e Release(). Depois dos métodos, IUnknown vêm os métodos de interface específicos. Por exemplo, considere IDemoGetType e IDemoStoreType. Conceitualmente, os vtables para os tipos seriam parecidos com o seguinte:

IDemoGetType    | IDemoStoreType
==================================
QueryInterface  | QueryInterface
AddRef          | AddRef
Release         | Release
GetString       | StoreString

Olhando DemoImplpara , já temos uma implementação para GetString() e StoreString(), mas e as IUnknown funções? Como implementar uma IUnknown instância está além do escopo deste tutorial, mas pode ser feito manualmente no ComWrappers. No entanto, neste tutorial, você permitirá que o tempo de execução manipule essa parte. Você pode obter a IUnknown implementação usando o ComWrappers.GetIUnknownImpl() método.

Pode parecer que você implementou todos os métodos, mas infelizmente, apenas as IUnknown funções são consumíveis em um vtable COM. Como o COM está fora do tempo de execução, você precisará criar ponteiros de função nativos para sua DemoImpl implementação. Isso pode ser feito usando ponteiros de função C# e o UnmanagedCallersOnlyAttribute. Você pode criar uma função para inserir no vtable criando uma static função que imita a assinatura da função COM. Segue-se um exemplo da assinatura COM para IDemoGetType.GetString() – lembre-se da COM ABI que o primeiro argumento é a própria instância.

[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);

A implementação do wrapper deve IDemoGetType.GetString() consistir em lógica de empacotamento e, em seguida, um despacho para o objeto gerenciado que está sendo encapsulado. Todo o estado de expedição está contido no argumento fornecido _this . O _this argumento será, na verdade, do tipo ComInterfaceDispatch*. Este tipo representa uma estrutura de baixo nível com um único campo, Vtableque será discutido mais adiante. Mais detalhes desse tipo e seu layout são um detalhe de implementação do tempo de execução e não devem ser dependidos. Para recuperar a instância gerenciada de uma ComInterfaceDispatch* instância, use o seguinte código:

IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);

Agora que você tem um método C# que pode ser inserido em um vtable, você pode construir o vtable. Observe o uso de para alocar memória de RuntimeHelpers.AllocateTypeAssociatedMemory() uma forma que funcione com assemblies descarregáveis .

GetIUnknownImpl(
    out IntPtr fpQueryInterface,
    out IntPtr fpAddRef,
    out IntPtr fpRelease);

// Local variables with increment act as a guard against incorrect construction of
// the native vtable. It also enables a quick validation of final size.
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&ABI.IDemoGetTypeManagedWrapper.GetString;
Debug.Assert(tableCount == idx);
s_IDemoGetTypeVTable = (IntPtr)vtable;

A atribuição de vtables é a primeira parte da implementação ComputeVtables(). Você também deve construir definições COM abrangentes para os tipos que você está planejando oferecer suporte – pense DemoImpl e quais partes dele devem ser utilizáveis a partir do COM. Usando os vtables construídos, agora você pode criar uma série de ComInterfaceEntry instâncias que representam a exibição completa do objeto gerenciado em COM.

s_DemoImplDefinitionLen = 2;
int idx = 0;
var entries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    sizeof(ComInterfaceEntry) * s_DemoImplDefinitionLen);
entries[idx].IID = IDemoGetType.IID_IDemoGetType;
entries[idx++].Vtable = s_IDemoGetTypeVTable;
entries[idx].IID = IDemoStoreType.IID_IDemoStoreType;
entries[idx++].Vtable = s_IDemoStoreVTable;
Debug.Assert(s_DemoImplDefinitionLen == idx);
s_DemoImplDefinition = entries;

A alocação de vtables e entradas para o Managed Object Wrapper pode e deve ser feita com antecedência, uma vez que os dados podem ser usados para todas as instâncias do tipo. O trabalho aqui pode ser executado em um static construtor ou um inicializador de módulo, mas deve ser feito com antecedência para que o método seja o ComputeVtables() mais simples e rápido possível.

protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags,
out int count)
{
    if (obj is DemoImpl)
    {
        count = s_DemoImplDefinitionLen;
        return s_DemoImplDefinition;
    }

    // Unknown type
    count = 0;
    return null;
}

Depois de implementar o ComputeVtables() método, a ComWrappers subclasse poderá produzir Managed Object Wrappers para instâncias de DemoImpl. Lembre-se de que o Wrapper de objeto gerenciado retornado da chamada para GetOrCreateComInterfaceForObject() é do tipo IUnknown*. Se a API nativa que está sendo passada para o wrapper requer uma interface diferente, uma Marshal.QueryInterface() interface para essa interface deve ser executada.

var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

Etapa 3 – Implementar CreateObject()

A construção de um Wrapper de Objeto Nativo tem mais opções de implementação e muito mais nuances do que a construção de um Wrapper de Objeto Gerenciado. A primeira questão a ser abordada é o quão permissiva a ComWrappers subclasse será no suporte a tipos COM. Para suportar todos os tipos COM, o que é possível, você precisará escrever uma quantidade substancial de código ou empregar alguns usos inteligentes do Reflection.Emit. Para este tutorial, você só suportará instâncias COM que implementem e IDemoGetTypeIDemoStoreType. Como você sabe que há um conjunto finito e restringiu que qualquer instância COM fornecida deve implementar ambas as interfaces, você pode fornecer um único wrapper definido estaticamente; no entanto, casos dinâmicos são comuns o suficiente em COM para que exploremos ambas as opções.

Wrapper de objeto nativo estático

Vamos examinar a implementação estática primeiro. O Native Object Wrapper estático envolve a definição de um tipo gerenciado que implementa as interfaces .NET e pode encaminhar as chamadas no tipo gerenciado para a instância COM. Segue-se um esboço aproximado do invólucro estático.

// See referenced sample for implementation.
class DemoNativeStaticWrapper
    : IDemoGetType
    , IDemoStoreType
{
    public string? GetString() =>
        throw new NotImplementedException();

    public void StoreString(int len, string? str) =>
        throw new NotImplementedException();
}

Para construir uma instância dessa classe e fornecê-la como um wrapper, você deve definir alguma política. Se esse tipo for usado como um wrapper, parece que, uma vez que implementa ambas as interfaces, a instância COM subjacente deve implementar ambas as interfaces também. Dado que você está adotando essa política, você precisará confirmar isso por meio de chamadas para Marshal.QueryInterface() a instância COM.

int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != 0)
{
    return null;
}

hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != 0)
{
    Marshal.Release(IDemoGetTypeInst);
    return null;
}

return new DemoNativeStaticWrapper()
{
    IDemoGetTypeInst = IDemoGetTypeInst,
    IDemoStoreTypeInst = IDemoStoreTypeInst
};

Wrapper de objeto nativo dinâmico

Os wrappers dinâmicos são mais flexíveis porque fornecem uma maneira de os tipos serem consultados em tempo de execução em vez de estaticamente. Para fornecer esse suporte, você utilizará IDynamicInterfaceCastable – mais detalhes podem ser encontrados aqui. Observe que DemoNativeDynamicWrapper apenas implementa esta interface. A funcionalidade que a interface fornece é uma chance de determinar que tipo é suportado em tempo de execução. A fonte deste tutorial faz uma verificação estática durante a criação, mas isso é simplesmente para compartilhamento de código, já que a verificação pode ser adiada até que uma chamada seja feita para DemoNativeDynamicWrapper.IsInterfaceImplemented().

// See referenced sample for implementation.
internal class DemoNativeDynamicWrapper
    : IDynamicInterfaceCastable
{
    public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) =>
        throw new NotImplementedException();

    public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) =>
        throw new NotImplementedException();
}

Vamos olhar para uma das interfaces que DemoNativeDynamicWrapper suportarão dinamicamente. O código a seguir fornece a implementação do uso do recurso de métodos de IDemoStoreType interface padrão.

[DynamicInterfaceCastableImplementation]
unsafe interface IDemoStoreTypeNativeWrapper : IDemoStoreType
{
    public static void StoreString(IntPtr inst, int len, string? str);

    void IDemoStoreType.StoreString(int len, string? str)
    {
        var inst = ((DemoNativeDynamicWrapper)this).IDemoStoreTypeInst;
        StoreString(inst, len, str);
    }
}

Há duas coisas importantes a observar neste exemplo:

  1. O DynamicInterfaceCastableImplementationAttribute atributo. Esse atributo é necessário em qualquer tipo retornado de um IDynamicInterfaceCastable método. Ele tem o benefício adicional de facilitar o corte de IL, o que significa que os cenários de AOT são mais confiáveis.
  2. O elenco para DemoNativeDynamicWrapper. Isto faz parte da natureza dinâmica do IDynamicInterfaceCastable. O tipo retornado é IDynamicInterfaceCastable.GetInterfaceImplementation() usado para "cobrir" o tipo que implementa IDynamicInterfaceCastableo . A essência aqui é que o this ponteiro não é o que finge ser, porque estamos permitindo um caso de DemoNativeDynamicWrapper até IDemoStoreTypeNativeWrapper.

Encaminhar chamadas para a instância COM

Independentemente de qual Native Object Wrapper é usado, você precisa da capacidade de invocar funções em uma instância COM. A implementação de pode servir como um exemplo de emprego unmanaged de ponteiros de IDemoStoreTypeNativeWrapper.StoreString() função C#.

public static void StoreString(IntPtr inst, int len, string? str)
{
    IntPtr strLocal = Marshal.StringToCoTaskMemUni(str);
    int hr = ((delegate* unmanaged<IntPtr, int, IntPtr, int>)(*(*(void***)inst + 3 /* IDemoStoreType.StoreString slot */)))(inst, len, strLocal);
    if (hr != 0)
    {
        Marshal.FreeCoTaskMem(strLocal);
        Marshal.ThrowExceptionForHR(hr);
    }
}

Vamos examinar a desreferenciação da instância COM para acessar sua implementação vtable. O COM ABI define que o primeiro ponteiro de um objeto é para o vtable do tipo e, a partir daí, o slot desejado pode ser acessado. Vamos supor que o endereço do objeto COM seja 0x10000. O primeiro valor do tamanho do ponteiro deve ser o endereço do vtable – neste exemplo 0x20000. Quando estiver no vtable, você procurará o quarto slot (índice 3 na indexação baseada em zero) para acessar a StoreString() implementação.

COM instance
0x10000  0x20000

VTable for IDemoStoreType
0x20000  <Address of QueryInterface>
0x20008  <Address of AddRef>
0x20010  <Address of Release>
0x20018  <Address of StoreString>

Ter o ponteiro da função permite que você despache para essa função de membro nesse objeto passando a instância do objeto como o primeiro parâmetro. Esse padrão deve parecer familiar com base nas definições de função da implementação do Managed Object Wrapper.

Depois que o CreateObject() método for implementado, a ComWrappers subclasse poderá produzir Native Object Wrappers para instâncias COM que implementam ambos e IDemoGetTypeIDemoStoreType.

IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);

Etapa 4 – Manipular os detalhes do tempo de vida do Native Object Wrapper

As ComputeVtables() implementações e CreateObject() cobriram alguns detalhes do tempo de vida do invólucro, mas há outras considerações. Embora isso possa ser um passo curto, também pode aumentar significativamente a complexidade do ComWrappers projeto.

Ao contrário do Managed Object Wrapper, que é controlado por chamadas para seus AddRef() métodos e Release() , o tempo de vida de um Native Object Wrapper é manipulado não deterministicamente pelo GC. A questão aqui é: quando o Native Object Wrapper chama Release() o IntPtr que representa a instância COM? Existem dois baldes gerais:

  1. O Finalizador do Native Object Wrapper é responsável por chamar o método da instância COM Release() . Este é o único momento em que é seguro chamar este método. Neste ponto, foi determinado corretamente pelo GC que não há outras referências ao Native Object Wrapper no tempo de execução do .NET. Pode haver complexidade aqui se você estiver apoiando adequadamente COM Apartments; para obter mais informações, consulte a seção Considerações adicionais.

  2. O Native Object Wrapper implementa IDisposable e chama Release()Dispose()o .

Nota

O IDisposable padrão só deve ser suportado se, durante a CreateObject() chamada, o CreateObjectFlags.UniqueInstance sinalizador foi passado. Se esse requisito não for seguido, é possível que os invólucros de objetos nativos descartados sejam reutilizados após serem descartados.

Usando a ComWrappers subclasse

Agora você tem uma ComWrappers subclasse que pode ser testada. Para evitar a criação de uma biblioteca nativa que retorna uma instância COM que implementa IDemoGetType e IDemoStoreType, você usará o Managed Object Wrapper e o tratará como uma instância COM – isso deve ser possível para passá-lo COM de qualquer maneira.

Vamos criar um Wrapper de Objeto Gerenciado primeiro. Instancie uma DemoImpl instância e exiba seu estado atual da cadeia de caracteres.

var demo = new DemoImpl();

string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");

Agora você pode criar uma instância de DemoComWrappers e um Managed Object Wrapper que você pode passar para um ambiente COM.

var cw = new DemoComWrappers();

IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

Em vez de passar o Wrapper de Objeto Gerenciado para um ambiente COM, finja que acabou de receber essa instância COM, para que você crie um Wrapper de Objeto Nativo para ele.

var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);

Com o Native Object Wrapper, você deve ser capaz de convertê-lo em uma das interfaces desejadas e usá-lo como um objeto gerenciado normal. Você pode examinar a DemoImpl instância e observar o impacto das operações no Native Object Wrapper que está encapsulando um Managed Object Wrapper que, por sua vez, encapsula a instância gerenciada.

var getter = (IDemoGetType)rcw;
var store = (IDemoStoreType)rcw;

string msg = "hello world!";
store.StoreString(msg.Length, msg);
Console.WriteLine($"Setting string through wrapper: {msg}");

value = demo.GetString();
Console.WriteLine($"Get string through managed object: {value}");

msg = msg.ToUpper();
demo.StoreString(msg.Length, msg.ToUpper());
Console.WriteLine($"Setting string through managed object: {msg}");

value = getter.GetString();
Console.WriteLine($"Get string through wrapper: {value}");

Como sua ComWrapper subclasse foi projetada para oferecer suporte CreateObjectFlags.UniqueInstanceao , você pode limpar o Native Object Wrapper imediatamente, em vez de esperar que um GC ocorra.

(rcw as IDisposable)?.Dispose();

Ativação COM com ComWrappers

A criação de objetos COM normalmente é realizada via Ativação COM – um cenário complexo fora do escopo deste documento. Para fornecer um padrão conceitual a ser seguido, apresentamos a CoCreateInstance() API, usada para ativação COM, e ilustramos como ela pode ser usada com ComWrapperso .

Suponha que você tenha o seguinte código C# em seu aplicativo. O exemplo abaixo usa CoCreateInstance() para ativar uma classe COM e o sistema de interoperabilidade COM interno para empacotar a instância COM para a interface apropriada. Observe que o uso de é limitado a uma asserção e é um caso de uso de typeof(I).GUID reflexão que pode afetar se o código for amigável para AOT.

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out object obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)obj;
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    [MarshalAs(UnmanagedType.Interface)] out object ppObj);

A conversão do acima para uso ComWrappers envolve a MarshalAs(UnmanagedType.Interface) remoção do P/Invoke e a execução do CoCreateInstance() marshalling manualmente.

static ComWrappers s_ComWrappers = ...;

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out IntPtr obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)s_ComWrappers.GetOrCreateObjectForComInstance(obj, CreateObjectFlags.None);
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    out IntPtr ppObj);

Também é possível abstrair funções de estilo de fábrica, como ActivateClass<I> incluir a lógica de ativação no construtor de classe para um Native Object Wrapper. O construtor pode usar a ComWrappers.GetOrRegisterObjectForComInstance() API para associar o objeto gerenciado recém-construído com a instância COM ativada.

Considerações adicionais

AOT nativo – A compilação Ahead-of-time (AOT) proporciona um custo de inicialização melhorado à medida que a compilação JIT é evitada. Remover a necessidade de compilação JIT também é frequentemente necessário em algumas plataformas. O suporte à AOT era um objetivo da API, mas qualquer implementação de wrapper deve ter cuidado para não introduzir inadvertidamente casos em que a ComWrappers AOT quebra, como o uso de reflexão. A Type.GUID propriedade é um exemplo de onde a reflexão é usada, mas de uma forma não óbvia. A Type.GUID propriedade usa reflexão para inspecionar os atributos do tipo e, em seguida, potencialmente o nome do tipo e contendo assembly para gerar seu valor.

Geração de código-fonte – A maioria do código necessário para interoperabilidade COM e uma ComWrappers implementação provavelmente pode ser gerada automaticamente por algumas ferramentas. A origem para ambos os tipos de wrappers pode ser gerada de acordo com as definições COM adequadas – por exemplo, Biblioteca de Tipos (TLB), IDL ou um Primary Interop Assembly (PIA).

Registro global – Como a ComWrappers API foi projetada como uma nova fase de interoperabilidade COM, ela precisava ter alguma maneira de se integrar parcialmente ao sistema existente. Existem métodos estáticos globalmente impactantes na API que permitem o ComWrappers registro de uma instância global para vários suportes. Esses métodos são projetados para ComWrappers instâncias que esperam fornecer suporte abrangente de interoperabilidade COM em todos os casos – semelhante ao sistema de interoperabilidade COM integrado.

Suporte ao Controlador de Referência – Este suporte é usado principalmente para cenários do WinRT e representa um cenário avançado. Para a maioria das ComWrapper implementações, um ou CreateObjectFlags.TrackerObject sinalizador CreateComInterfaceFlags.TrackerSupport deve lançar um NotSupportedException. Se você quiser habilitar esse suporte, talvez em uma plataforma Windows ou até mesmo não-Windows, é altamente recomendável fazer referência à cadeia de ferramentas C#/WinRT.

Além do tempo de vida, do sistema de tipo e dos recursos funcionais que são discutidos ComWrappers anteriormente, uma implementação compatível com COM requer considerações adicionais. Para qualquer implementação que será usada na plataforma Windows, há as seguintes considerações:

  • Apartamentos – A estrutura organizacional da COM para threading é chamada de "Apartamentos" e possui regras rígidas que devem ser seguidas para operações estáveis. Este tutorial não implementa Native Object Wrappers com reconhecimento de apartamento, mas qualquer implementação pronta para produção deve estar ciente do apartamento. Para fazer isso, recomendamos usar a API introduzida RoGetAgileReference no Windows 8. Para versões anteriores ao Windows 8, considere a Tabela de Interface Global.

  • Segurança – O COM fornece um modelo de segurança avançado para ativação de classe e permissão por proxy.