Compartilhar via


Tutorial: Usar a API ComWrappers

Neste tutorial, você aprenderá a criar corretamente uma subclasse do tipo ComWrappers para fornecer uma solução de interoperabilidade COM otimizada e amigável ao 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 definições de interface a seguir. Essas interfaces e suas implementações demonstrarão:

  • Marshalling e unmarshalling de tipos através da fronteira 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 além.

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

Observação

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

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 runtime do .NET 5+. Isso significa que muitas das conveniências que existem com o sistema de interoperabilidade COM interno não estão presentes e precisam 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 aprimoradas ao exigir que o processo de criação e aquisição de wrappers passe pela ComWrappers 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 executado por uma solução de geração de origem. Por exemplo, a cadeia de ferramentas C#/WinRT é uma solução de geração de código que é construída sobre ComWrappers para fornecer suporte de interoperabilidade WinRT.

Implementar uma ComWrappers subclasse

Fornecer uma ComWrappers subclasse significa dar informações suficientes ao runtime do .NET para criar e registrar wrappers para objetos gerenciados que estão sendo projetados em COM, assim como para objetos COM que estão sendo projetados no .NET. Antes de examinarmos o esboço da subclasse, devemos definir alguns termos.

Wrapper de Objeto Gerenciado – Objetos .NET gerenciados exigem wrappers para habilitar o uso de um ambiente não-.NET. Esses wrappers são historicamente chamados de Wrappers Chamáveis COM (CCW).

Wrapper de Objeto Nativo – objetos COM implementados em uma linguagem não .NET exigem wrappers para habilitar o uso a partir do .NET. Esses wrappers são historicamente chamados RCW (Runtime Callable Wrappers).

Etapa 1 – Definir métodos para implementar e entender 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 métodos ComputeVtables() e CreateObject() criam um Wrapper de Objeto Gerenciado e um Wrapper de Objeto Nativo, respectivamente. O ReleaseObjects() método é usado pelo runtime para fazer uma solicitação para que a coleção fornecida de wrappers seja "liberada" do objeto nativo subjacente. Na maioria dos casos, o corpo do ReleaseObjects() método pode simplesmente gerar NotImplementedException, pois ele é chamado apenas em um cenário avançado envolvendo a estrutura do Rastreador de Referência.

// 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ê deseja dar suporte. Para este tutorial, daremos suporte às duas interfaces definidas anteriormente (IDemoGetType e IDemoStoreType) e a 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 dar suporte. Nesse caso, porém, sabemos apenas as interfaces COM nas quais estamos interessados, não nas classes COM. As interfaces que estão sendo consumidas do lado COM são as mesmas que estamos projetando do lado do .NET (ou seja, IDemoGetType e IDemoStoreType).

Não implementaremos ReleaseObjects() neste tutorial.

Etapa 2 – Implementar ComputeVtables()

Vamos começar com o Envoltório de Objeto Gerenciado – esses envoltórios são mais fáceis. Você criará uma Tabela de Método Virtual, ou vtable, para cada interface para projetá-las no ambiente COM. Para este tutorial, você definirá uma vtable como uma sequência de ponteiros, em que cada ponteiro representa uma implementação de uma função em uma interface – a ordem é muito importante aqui. No COM, cada interface herda de IUnknown. O IUnknown tipo tem três métodos definidos na seguinte ordem: QueryInterface(), AddRef()e Release(). Após os métodos IUnknown, vêm os métodos de interface específicos. Por exemplo, considere IDemoGetType e IDemoStoreType. Conceitualmente, as vtables para os tipos seriam como as seguintes:

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

Olhando para DemoImpl, já temos uma implementação para GetString() e StoreString(), mas e quanto às funções IUnknown? Como implementar uma IUnknown instância está além do escopo deste tutorial, mas ela pode ser feita manualmente em ComWrappers. No entanto, neste tutorial, você deixará que o tempo de execução lide com 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 uma vtable COM. Como o COM está fora do runtime, 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 na vtable criando uma static função que imita a assinatura da função COM. A seguir um exemplo da assinatura COM para IDemoGetType.GetString() – lembre-se da ABI COM que o primeiro argumento é a própria instância.

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

A implementação do IDemoGetType.GetString() wrapper deve envolver lógica de marshalling e, em seguida, uma chamada para o objeto gerenciado que está sendo encapsulado. Todo o estado para expedição está contido no argumento fornecido _this . O _this argumento será, na verdade, do tipo ComInterfaceDispatch*. Esse tipo representa uma estrutura de baixo nível com um único campo, Vtableque será discutida posteriormente. Mais detalhes desse tipo e seu layout são um detalhe de implementação do tempo de execução e não devem ser considerados confiáveis. 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 uma vtable, você pode construir a vtable. Observe o uso de RuntimeHelpers.AllocateTypeAssociatedMemory() para alocar memória de 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 alocaçã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 dar suporte – pense no DemoImpl e em quais partes dele devem ser utilizáveis pelo COM. Usando as vtables construídas, você agora pode criar uma série de instâncias de ComInterfaceEntry que representam a visã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 antecipadamente, pois os dados podem ser usados para todas as instâncias do tipo. O trabalho aqui pode ser executado em um static construtor ou 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 método ComputeVtables(), a subclasse ComWrappers poderá produzir invólucros de objetos gerenciados 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 exigir uma interface diferente, uma Marshal.QueryInterface() para essa interface deverá 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 construir um Wrapper de Objeto Gerenciado. A primeira pergunta a ser abordada é o quão permissiva a ComWrappers subclasse será em tipos COM de suporte. Para dar suporte a todos os tipos COM, o que é possível, você precisará escrever uma quantidade substancial de código ou empregar alguns usos inteligentes de Reflection.Emit. Para este tutorial, você só oferecerá suporte a instâncias COM que implementam ambos IDemoGetType e IDemoStoreType. 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 wrapper único e definido estaticamente; no entanto, casos dinâmicos são comuns o suficiente em COM que exploraremos ambas as opções.

Wrapper de objeto nativo estático

Vamos examinar a implementação estática primeiro. O Wrapper de Objeto Nativo estático envolve a definição de um tipo gerenciado que implementa as interfaces do .NET e pode encaminhar as chamadas no tipo gerenciado para uma instância COM. Um esboço aproximado do wrapper estático segue.

// 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, como ele implementa ambas as interfaces, a instância COM subjacente também deve implementar ambas as interfaces. Considerando que você está adotando essa política, será necessário confirmar isso por meio de chamadas para Marshal.QueryInterface() na 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
};

Envoltório de Objeto Nativo Dinâmico

Os wrappers dinâmicos são mais flexíveis porque fornecem uma maneira de os tipos serem consultados em runtime em vez de estaticamente. Para fornecer esse suporte, você usará IDynamicInterfaceCastable. Observe que DemoNativeDynamicWrapper só implementa essa interface. A funcionalidade fornecida pela interface é uma chance de determinar qual tipo tem suporte no runtime. A origem deste tutorial faz uma verificação estática durante a criação, mas isso é simplesmente para o compartilhamento de código, pois a verificação pode ser adiada até que uma chamada seja feita a 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 examinar uma das interfaces que DemoNativeDynamicWrapper dará suporte dinâmico. O código a seguir fornece a implementação de IDemoStoreType usando o recurso de métodos de 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 serem observadas 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 a eliminação de IL, o que significa que os cenários de AOT se tornam mais confiáveis.
  2. A conversão para DemoNativeDynamicWrapper. Isso faz parte da natureza dinâmica de IDynamicInterfaceCastable. O tipo retornado de IDynamicInterfaceCastable.GetInterfaceImplementation() é usado para "envolver" o tipo que implementa IDynamicInterfaceCastable. A essência aqui é que o this ponteiro não é o que ele finge ser porque estamos permitindo um caso de DemoNativeDynamicWrapper para IDemoStoreTypeNativeWrapper.

Encaminhar chamadas para a instância COM

Independentemente de qual Wrapper de Objeto Nativo é usado, você precisa ter a capacidade de invocar funções em uma instância COM. A implementação de IDemoStoreTypeNativeWrapper.StoreString() pode servir como exemplo do uso de ponteiros de função C# em unmanaged.

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 o desreferenciamento da instância COM para acessar sua implementação vtable. A ABI COM define que o primeiro ponteiro de um objeto é para a 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 da vtable – neste exemplo 0x20000. Quando estiver na vtable, procure o quarto slot (índice 3 em indexação que começa do zero) para acessar a implementação StoreString().

COM instance
0x10000  0x20000

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

Possuir o ponteiro de função permite que você chame essa função 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 Wrapper de Objeto Gerenciado.

Depois que o método CreateObject() for implementado, a subclasse ComWrappers poderá produzir Wrappers de Objeto Nativo para instâncias COM que implementam tanto IDemoGetType quanto IDemoStoreType.

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

Etapa 4 – Manipular detalhes do ciclo de vida do Envoltório de Objeto Nativo

As implementações ComputeVtables() e CreateObject() abordaram alguns detalhes sobre o ciclo de vida do wrapper, mas há outras considerações. Embora isso possa ser uma etapa curta, também pode aumentar significativamente a complexidade do ComWrappers design.

Ao contrário do Wrapper de Objeto Gerenciado, que é controlado por chamadas para seus métodos AddRef() e Release(), o tempo de vida de um Wrapper de Objeto Nativo é tratado de forma não determinística pelo GC. A pergunta aqui é: quando o Wrapper de Objeto Nativo chama Release() o IntPtr que representa a instância COM? Há duas categorias gerais:

  1. O Finalizador do Wrapper de Objeto Nativo é responsável por chamar o método da instância COM Release(). Esta é a única vez em que é seguro chamar esse método. Neste ponto, foi corretamente determinado pelo GC que não há outras referências ao Objeto Nativo Wrapper no ambiente de execução do .NET. Pode haver complexidade aqui se você estiver dando suporte corretamente a apartamentos COM; para obter mais informações, consulte a seção Considerações adicionais.

  2. O Invólucro de Objeto Nativo implementa IDisposable e chama Release() em Dispose().

Observação

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

Usando a ComWrappers subclasse

Agora você tem uma ComWrappers subclasse que pode ser testada. Para evitar a criação de uma biblioteca nativa que retorne uma instância COM implementando IDemoGetType e IDemoStoreType, você usará o Wrapper de Objeto Gerenciado e a tratará como uma instância COM. Isso é necessário para que seja possível passá-la como uma instância COM.

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

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 Wrapper de Objeto Gerenciado, que você pode então 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; em vez disso, você criará um Wrapper de Objeto Nativo para ela.

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

Com o Wrapper de Objeto Nativo, 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 Wrapper de Objeto Nativo que encapsula um Wrapper de Objeto Gerenciado, o qual, 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 dar suporte CreateObjectFlags.UniqueInstance, você pode limpar o Wrapper de Objeto Nativo 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 é executada por meio da Ativação COM – um cenário complexo fora do escopo deste documento. Para fornecer um padrão conceitual a seguir, apresentamos a CoCreateInstance() API, usada para ativação de COM e ilustramos como ela pode ser usada com ComWrappers.

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 marcar a instância COM para a interface apropriada. Observe que o uso de typeof(I).GUID é limitado a uma assertiva e é um caso de uso de reflexão que pode impactar a compatibilidade do código com 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 CoCreateInstance() P/Invoke e a execução manual do marshalling.

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>, ao incluir a lógica de ativação no construtor da classe para um Wrapper Nativo do Objeto. O construtor pode usar a ComWrappers.GetOrRegisterObjectForComInstance() API para associar o objeto gerenciado recém-construído à instância COM ativada.

Considerações adicionais

A compilação antecipada (AOT) nativa fornece um custo de inicialização reduzido à medida que a compilação JIT é evitada. A remoção da necessidade de compilação JIT também é frequentemente necessária em algumas plataformas. O suporte à AOT era uma meta da ComWrappers API, mas qualquer implementação de wrapper deve ter cuidado para não introduzir inadvertidamente casos em que o AOT é interrompido, como o uso de reflexão. A Type.GUID propriedade é um exemplo de onde a reflexão é usada, mas de forma não óbvia. A propriedade Type.GUID usa reflexão para inspecionar os atributos do tipo e, potencialmente, o nome do tipo e o assembly onde ele está contido, a fim de gerar seu valor.

Geração de código fonte – a maior parte do código necessário para a 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 dadas as definições DE COM adequadas – por exemplo, TLB (Biblioteca de Tipos), IDL ou PIA (Assembly de Interoperabilidade Primário).

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 que impactam globalmente na API ComWrappers que permitem o registro de uma instância global para diferentes tipos de suporte. Esses métodos são projetados para ComWrappers instâncias que esperam fornecer suporte de interoperabilidade COM abrangente em todos os casos, semelhante a um sistema de interoperabilidade COM embutido.

Suporte ao Rastreador de Referência – esse suporte é usado principalmente para cenários do WinRT e representa um cenário avançado. Para a maioria das ComWrapper implementações, um CreateComInterfaceFlags.TrackerSupport ou CreateObjectFlags.TrackerObject sinalizador 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 referenciar a cadeia de ferramentas C#/WinRT.

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

  • Apartamentos – A estrutura organizacional do COM para threading é denominada "Apartamentos" e possui regras rígidas que precisam ser seguidas para garantir operações estáveis. Este tutorial não implementa wrappers de objeto nativo que reconhecem apartamentos, mas qualquer implementação pronta para produção deve ser compatível com apartamentos. Para fazer isso, recomendamos usar a RoGetAgileReference API introduzida no Windows 8. Para versões anteriores ao Windows 8, considere a Tabela de Interface Global.

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