Tutorial: usar a API ComWrappers
Neste tutorial, você aprenderá a subclasse corretamente o tipo ComWrappers
para fornecer uma solução de interoperabilidade COM otimizada e amigável ao AOT. Antes de iniciar este tutorial, você deve conhecer o 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:
- Tipos de marshalling e unmarshalling no 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 superior.
Todo o código-fonte usado neste tutorial está disponível no repositório dotnet/samples.
Observação
No SDK 8 do .NET e em versões posteriores, é fornecido um gerador de origem para gerar automaticamente uma implementação de API ComWrappers
. Para obter mais informações, confira Geração de origem do ComWrappers
.
Definições de C#
interface IDemoGetType
{
string? GetString();
}
interface IDemoStoreType
{
void StoreString(int len, string? str);
}
Definições de 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 design ComWrappers
A API ComWrappers
foi projetada para fornecer a interação mínima necessária para realizar a interoperabilidade COM junto ao runtime do .NET 5+. Isso significa que muitas das belezas que existem com o sistema de interoperabilidade COM interno 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:
- Identificação eficiente de objeto (por exemplo, mapeamento entre uma
IUnknown*
instância e um objeto gerenciado). - Interação do Coletor de Lixo (GC).
Essas eficiências são realizadas exigindo a criação e a aquisição do wrapper para passar pela API ComWrappers
.
Como a API ComWrappers
tem 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 origem criada com base no suporte de interoperabilidade do WinRT ComWrappers
.
Implementar uma subclasse ComWrappers
Fornecer uma subclasse ComWrappers
significa dar informações suficientes ao runtime do .NET para criar e gravar wrappers para objetos gerenciados que estão sendo projetados em objetos COM e COM sendo projetados no .NET. Antes de examinarmos uma estrutura de tópicos da subclasse, devemos definir alguns termos.
Wrapper de Objeto Gerenciado – Objetos .NET gerenciados exigem wrappers para habilitar o uso de um ambiente de non-.NET. Esses wrappers são historicamente chamados de CCW (Com Callable Wrappers).
Wrapper de Objeto Nativo – objetos COM implementados em uma linguagem non-.NET exigem wrappers para habilitar o uso do .NET. Esses wrappers são historicamente chamados de RCW (Runtime Callable Wrappers).
Etapa 1 – Definir métodos para implementar e entender sua intenção
Para estender o tipo ComWrappers
, 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 Wrapper de Objeto Nativo, respectivamente. O método ReleaseObjects()
é 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 método ReleaseObjects()
pode simplesmente lançar 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 método ComputeVtables()
, 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 método CreateObject()
, você também precisará determinar o que deseja dar suporte. Nesse caso, porém, só sabemos as interfaces COM nas quais estamos interessados, não as classes COM. As interfaces que estão sendo consumidas do lado COM são iguais às 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 Wrapper de Objeto Gerenciado – esses wrappers são mais fáceis. Você criará uma Tabela de métodos virtuais, 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. Em COM, cada interface herda de IUnknown
. O tipo IUnknown
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 semelhantes às seguintes:
IDemoGetType | IDemoStoreType
==================================
QueryInterface | QueryInterface
AddRef | AddRef
Release | Release
GetString | StoreString
Analisando DemoImpl
, já temos uma implementação para GetString()
e StoreString()
, mas e as funções IUnknown
? Como implementar uma instância IUnknown
está além do escopo deste tutorial, mas pode ser feito manualmente em ComWrappers
. No entanto, neste tutorial, você permitirá que o runtime manipule essa parte. Você pode obter a implementação IUnknown
usando o método ComWrappers.GetIUnknownImpl()
.
Pode parecer que você implementou todos os métodos, mas, infelizmente, apenas as funções IUnknown
são consumíveis em uma vtable COM. Como o COM está fora do runtime, você precisará criar ponteiros de função nativos para a implementação DemoImpl
. 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 função static
que imita a assinatura da função COM. Veja a seguir um exemplo da assinatura COM para IDemoGetType.GetString()
: lembre-se da ABI COM de que o primeiro argumento é a própria instância.
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);
A implementação do wrapper IDemoGetType.GetString()
deve consistir em lógica de marshalling e, em seguida, uma expedição para o objeto gerenciado que está sendo encapsulado. Todo o estado para expedição está contido no argumento _this
fornecido. O argumento _this
será realmente do tipo ComInterfaceDispatch*
. Esse tipo representa uma estrutura de baixo nível com um único campo, Vtable
, que será discutido posteriormente. Detalhes adicionais desse tipo e seu layout são um detalhe de implementação do runtime e não devem ser dependentes. Para recuperar a instância gerenciada de uma instância ComInterfaceDispatch*
, 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 da alocação de memória de RuntimeHelpers.AllocateTypeAssociatedMemory()
uma forma que funcione com assemblies descarregados.
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 de COM abrangentes para tipos que você está planejando dar suporte – pense DemoImpl
e quais partes dela devem ser utilizáveis do COM. Usando as vtables construídas, agora você pode criar uma série de instâncias ComInterfaceEntry
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 Wrapper de Objeto Gerenciado 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 construtor static
ou inicializador de módulo, mas deve ser feito antecipadamente para que o método ComputeVtables()
seja o 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 wrappers de objeto gerenciado para instâncias de DemoImpl
. Lembre-se de que o Wrapper de Objeto Gerenciado retornado da chamada é 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 subclasse ComWrappers
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 existe 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 possamos explorar ambas as opções.
Wrapper de Objeto Nativo Estático
Vamos examinar primeiro a implementação estática. O Wrapper de Objeto Nativo 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 uma estrutura de tópicos aproximada do wrapper 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, 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, precisará 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
};
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
só implementa essa interface. A funcionalidade que a interface fornece é uma chance de determinar qual tipo tem suporte no 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, pois 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();
}
Vejamos uma das interfaces que DemoNativeDynamicWrapper
aceitará dinamicamente. O código a seguir fornece a implementação de IDemoStoreType
usando o recurso 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á algumas coisas importantes a serem observadas sobre este exemplo.
- O atributo
DynamicInterfaceCastableImplementationAttribute
. Esse atributo é necessário em qualquer tipo retornado de um métodoIDynamicInterfaceCastable
. Ele tem o benefício adicional de facilitar o corte de IL, o que significa que os cenários AOT são mais confiáveis. - A conversão para
DemoNativeDynamicWrapper
. Isso faz parte da natureza dinâmica deIDynamicInterfaceCastable
. O tipo retornado deIDynamicInterfaceCastable.GetInterfaceImplementation()
é usado para "cobrir" o tipo que implementaIDynamicInterfaceCastable
. A essência aqui é que o ponteirothis
não é o que finge ser porque estamos permitindo um caso deDemoNativeDynamicWrapper
aIDemoStoreTypeNativeWrapper
.
Encaminhar chamadas para a instância COM
Independentemente de qual Wrapper de Objeto Nativo é usado, você precisa da capacidade de invocar funções em uma instância COM. A implementação de IDemoStoreTypeNativeWrapper.StoreString()
pode servir como um exemplo de emprego de ponteiros de função C# 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 a remoção de refrência da instância COM para acessar sua implementação de vtable. A COM ABI 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
. Depois de estar na vtable, procure o quarto slot (índice 3 na indexação baseada em 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>
Ter o ponteiro de 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 Wrapper de Objeto Gerenciado.
Depois que o método CreateObject()
for implementado, a subclasse ComWrappers
poderá produzir Native Object Wrappers para instâncias COM que implementam IDemoGetType
e IDemoStoreType
.
IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);
Etapa 4 – Manipular detalhes de tempo de vida do Wrapper de Objeto Nativo
As implementações ComputeVtables()
e CreateObject()
cobriram alguns detalhes do tempo de vida do wrapper, mas há outras considerações. Embora isso possa ser uma etapa curta, também pode aumentar significativamente a complexidade do design ComWrappers
.
Ao contrário do Managed Object Wrapper, que é controlado por chamadas para seus métodos AddRef()
e Release()
, o tempo de vida de um Native Object Wrapper é tratado de forma não determinística pelo GC. A questão aqui é: quando o Native Object Wrapper chama Release()
no IntPtr
que representa a instância COM? Há dois buckets gerais:
O Finalizador do Wrapper de Objeto Nativo é responsável por chamar o método
Release()
da instância COM. Esta é a única vez em que é seguro chamar esse método. Neste ponto, foi determinado corretamente pelo GC que não há outras referências ao Wrapper de Objeto Nativo no runtime do .NET. Pode haver complexidade aqui se você estiver dando suporte adequado a apartamentos COM; para obter mais informações, consulte a seção Considerações adicionais.O Wrapper de Objeto Nativo implementa
IDisposable
e chamaRelease()
emDispose()
.
Observação
O padrão IDisposable
só deverá ter suporte se, durante a chamada CreateObject()
, o sinalizador CreateObjectFlags.UniqueInstance
tiver sido passado. Se esse requisito não for seguido, é possível que os Wrappers de Objeto Nativo descartados sejam reutilizados após serem descartados.
Usando a subclasse ComWrappers
Agora você tem uma subclasse ComWrappers
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 Wrapper de Objeto Gerenciado 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 instância DemoImpl
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 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 você acabou de receber essa instância COM, para criar um Wrapper de Objeto Nativo para ele.
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 instância DemoImpl
e observar o impacto das operações no Wrapper de Objeto Nativo que está encapsulando um Wrapper de Objeto Gerenciado que, por sua vez, está encapsulando 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 subclasse ComWrapper
foi projetada para dar suporte CreateObjectFlags.UniqueInstance
, você pode limpar o Wrapper de Objeto Nativo imediatamente em vez de aguardar 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 API CoCreateInstance()
, usada para ativação do COM e ilustramos como ela pode ser usada com ComWrappers
.
Suponha que você tenha o código C# a seguir em seu aplicativo. O exemplo a seguir usa CoCreateInstance()
para ativar uma classe COM e o sistema de interoperabilidade COM interno para levar a instância COM para a interface apropriada. Observe que o uso de typeof(I).GUID
é limitado a uma declaração e é um caso de uso de reflexão que pode afetar se o código for amigável ao 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);
Converter o acima para usar ComWrappers
envolve remover o MarshalAs(UnmanagedType.Interface)
do P/Invoke CoCreateInstance()
e realizar o empacotamento 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 Wrapper de Objeto Nativo. O construtor pode usar a API ComWrappers.GetOrRegisterObjectForComInstance()
para associar o objeto gerenciado recém-construído à instância COM ativada.
Considerações adicionais
AOT nativo – A compilação AOT (Ahead-of-time) oferece custo de inicialização aprimorado, pois a compilação JIT é evitada. A remoção da necessidade de compilação JIT também costuma ser necessária em algumas plataformas. O suporte à AOT era uma meta da API ComWrappers
, 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 propriedade Type.GUID
é um exemplo do local em que a reflexão é usada, mas de uma forma não óbvia. A propriedade Type.GUID
usa reflexão para inspecionar os atributos do tipo e, em seguida, potencialmente o nome do tipo e o assembly que contém para gerar seu valor.
Geração de origem – A maior parte do código necessário para a interoperabilidade COM e uma implementação ComWrappers
provavelmente pode ser gerada automaticamente por algumas ferramentas. A origem para ambos os tipos de wrappers pode ser gerada considerando as definições de COM adequadas – por exemplo, TLB (Biblioteca de Tipos), IDL ou pia (assembly de interoperabilidade primário).
Registro global – como a API ComWrappers
foi projetada como uma nova fase de interoperabilidade COM, ela precisava ter alguma forma de integrar parcialmente ao sistema existente. Há métodos estáticos globalmente afetados na API que permitem o registro ComWrappers
de uma instância global para vários suportes. Esses métodos são projetados para instâncias ComWrappers
que esperam fornecer suporte abrangente de interoperabilidade COM em todos os casos, semelhante ao sistema de interoperabilidade COM interno.
Suporte ao Rastreador de Referência – esse suporte é usado principalmente para cenários WinRT e representa um cenário avançado. Para a maioria das implementações de ComWrapper
, um sinalizador CreateComInterfaceFlags.TrackerSupport
ou CreateObjectFlags.TrackerObject
deve gerar 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 tipos e dos recursos funcionais discutidos anteriormente, uma implementação de ComWrappers
apropriada ao 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 é chamada de "Apartamentos" e tem regras rígidas que devem ser seguidas para operações estáveis. Este tutorial não implementa wrappers de objeto nativo com reconhecimento de apartamento, mas qualquer implementação pronta para produção deve estar ciente do apartamento. Para fazer isso, é recomendável usar a API
RoGetAgileReference
introduzida no Windows 8. Para versões anteriores a 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 proxied.