Tutorial: Usa la API ComWrappers
En este tutorial, aprenderás a subclasificar correctamente el ComWrappers
tipo para proporcionar una solución de interoperabilidad COM optimizada y compatible con AOT. Antes de comenzar este tutorial, debes estar familiarizado con COM, su arquitectura y las soluciones de interoperabilidad COM existentes.
En este tutorial, implementarás las siguientes definiciones de interfaz. Estas interfaces y sus implementaciones mostrarán:
- Serialización y desagrupación de tipos a través del límite COM/.NET.
- Dos enfoques distintos para consumir objetos COM nativos en .NET.
- Patrón recomendado para habilitar la interoperabilidad COM personalizada en .NET 5 y versiones posteriores.
Todo el código fuente usado en este tutorial está disponible en el repositorio dotnet/samples.
Nota:
En el SDK de .NET 8 y versiones posteriores, se proporciona un generador de origen para generar automáticamente una implementación de API ComWrappers
. Para obtener más información, consulte ComWrappers
generación de origen.
C# definiciones
interface IDemoGetType
{
string? GetString();
}
interface IDemoStoreType
{
void StoreString(int len, string? str);
}
Definiciones 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;
};
Información general del diseño ComWrappers
La ComWrappers
API se diseñó para proporcionar la interacción mínima necesaria para lograr la interoperabilidad COM con el entorno de ejecución de .NET 5+. Esto significa que muchas de las ventajas que existen con el sistema de interoperabilidad COM integrado no están presentes y deben crearse a partir de bloques de creación básicos. Las dos responsabilidades principales de la API son:
- Identificación eficaz de objetos (por ejemplo, asignación entre una
IUnknown*
instancia y un objeto administrado). - Interacción del recolector de elementos no utilizados (GC).
Estas eficiencias se logran al requerir la creación y adquisición del contenedor para pasar por la ComWrappers
API.
Dado que la ComWrappers
API tiene tantas responsabilidades, significa que la mayoría del trabajo de interoperabilidad se debe controlar por parte del consumidor, esto es cierto. Sin embargo, el trabajo adicional es en gran medida mecánico y se puede realizar mediante una solución de generación de origen. Por ejemplo, la cadena de herramientas de C#/WinRT es una solución de generación de origen basada en ComWrappers
para proporcionar compatibilidad con la interoperabilidad de WinRT.
Implementar una ComWrappers
subclase
Proporcionar una ComWrappers
subclase significa proporcionar suficiente información al entorno de ejecución de .NET para crear y registrar contenedores para objetos administrados que se proyectan en objetos COM y COM que se proyectan en .NET. Antes de examinar un esquema de la subclase, debemos definir algunos términos.
Contenedor de objetos administrados: los objetos .NET administrados requieren contenedores para habilitar el uso desde un entorno de non-.NET. Estos contenedores se denominan históricamente contenedores invocables COM (CCW).
Contenedor de objetos nativos : los objetos COM que se implementan en un lenguaje non-.NET requieren contenedores para habilitar el uso desde .NET. Estos contenedores se denominan históricamente contenedores invocables en tiempo de ejecución (RCW).
Paso 1: Definición de métodos para implementar y comprender su intención
Para ampliar el ComWrappers
tipo, debe implementar los tres métodos siguientes. Cada uno de estos métodos representa la participación del usuario en la creación o eliminación de un tipo de contenedor. Los métodos ComputeVtables()
y CreateObject()
crean un contenedor de objetos administrados y un contenedor de objetos nativos, respectivamente. El ReleaseObjects()
tiempo de ejecución usa el método para realizar una solicitud para que la colección proporcionada de contenedores se «libere» del objeto nativo subyacente. En la mayoría de los casos, el cuerpo del ReleaseObjects()
método simplemente puede iniciar NotImplementedException, ya que solo se llama en un escenario avanzado que implica el marco de seguimiento de referencias.
// 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 el método ComputeVtables()
, decida qué tipos administrados deseas admitir. En este tutorial, se admitirán las dos interfaces definidas anteriormente (IDemoGetType
y IDemoStoreType
) y un tipo administrado que implementa las dos interfaces (DemoImpl
).
class DemoImpl : IDemoGetType, IDemoStoreType
{
string? _string;
public string? GetString() => _string;
public void StoreString(int _, string? str) => _string = str;
}
Para el método CreateObject()
, también deberá determinar lo que le gustaría admitir. Sin embargo, en este caso, solo conocemos las interfaces COM que estamos interesados, no las clases COM. Las interfaces que se consumen desde el lado COM son las mismas que las que proyectamos desde el lado de .NET (es decir, IDemoGetType
y IDemoStoreType
).
No implementaremos ReleaseObjects()
en este tutorial.
Paso 2: Implementar ComputeVtables()
Comencemos con el contenedor de objetos administrados: estos contenedores son más fáciles. Crearás una tabla de métodos virtuales o vtable para cada interfaz con el fin de proyectarlas en el entorno COM. En este tutorial, definirás una tabla virtual como una secuencia de punteros, donde cada puntero representa una implementación de una función en una interfaz; el orden es muy importante aquí. En COM, cada interfaz hereda de IUnknown
. El tipo IUnknown
tiene tres métodos definidos en el orden siguiente: QueryInterface()
, AddRef()
y Release()
. Después de que los métodosIUnknown
lleguen a los métodos de interfaz específicos. Por ejemplo, considera IDemoGetType
y IDemoStoreType
. Conceptualmente, las tablas virtuales de los tipos tendrían el siguiente aspecto:
IDemoGetType | IDemoStoreType
==================================
QueryInterface | QueryInterface
AddRef | AddRef
Release | Release
GetString | StoreString
Al examinar DemoImpl
, ya tenemos una implementación para GetString()
y StoreString()
, pero ¿qué ocurre con las IUnknown
funciones? La implementación de una IUnknown
instancia está fuera del ámbito de este tutorial, pero se puede realizar manualmente en ComWrappers
. Sin embargo, en este tutorial, permitirás que el entorno de ejecución controle esa parte. Puede obtener la IUnknown
implementación mediante el ComWrappers.GetIUnknownImpl()
método.
Puede parecer que has implementado todos los métodos, pero desafortunadamente, solo las funciones IUnknown
son consumibles en una vtable COM. Dado que COM está fuera del entorno de ejecución, deberá crear punteros de función nativos a la DemoImpl
implementación. Esto se puede hacer mediante punteros de función de C# y UnmanagedCallersOnlyAttribute
. Puedes crear una función para insertarla en la tabla virtual mediante la creación de una static
función que imita la firma de la función COM. A continuación se muestra un ejemplo de la firma COM para IDemoGetType.GetString()
: recuerda que la ABI COM que el primer argumento es la propia instancia.
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);
La implementación contenedora de IDemoGetType.GetString()
debe constar de lógica de serialización y, a continuación, un envío al objeto administrado que se va a encapsular. Todo el estado de distribución se encuentra dentro del argumento proporcionado _this
. El argumento _this
será realmente de tipo ComInterfaceDispatch*
. Este tipo representa una estructura de bajo nivel con un único campo, Vtable
, que se describirá más adelante. Los detalles adicionales de este tipo y su diseño son un detalle de implementación del tiempo de ejecución y no deben depender de él. Para recuperar la instancia administrada de una ComInterfaceDispatch*
instancia, usa el código siguiente:
IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);
Ahora que tienes un método de C# que se puede insertar en una tabla virtual, puede construir la tabla virtual. Ten en cuenta el uso de RuntimeHelpers.AllocateTypeAssociatedMemory()
para asignar memoria de forma que funcione con ensamblados descargables.
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;
La asignación de vtables es la primera parte de la implementación de ComputeVtables()
. También debes crear definiciones COM completas para los tipos que planeas admitir: piensa DemoImpl
y qué partes de este deben ser utilizables desde COM. Con las tablas virtuales construidas, ahora puedes crear una serie de ComInterfaceEntry
instancias que representen la vista completa del objeto administrado en 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;
La asignación de vtables y entradas para el contenedor de objetos administrados puede y debe realizarse con antelación, ya que los datos se pueden usar para todas las instancias del tipo. El trabajo aquí se puede realizar en un static
constructor o en un inicializador de módulo, pero debe realizarse con antelación para que el ComputeVtables()
método sea lo más sencillo y rápido posible.
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;
}
Una vez implementado el método ComputeVtables()
, la subclase ComWrappers
podrá generar contenedores de objetos administrados para instancias de DemoImpl
. Ten en cuenta que el contenedor de objetos administrados devuelto de la llamada a GetOrCreateComInterfaceForObject()
es de tipo IUnknown*
. Si la API nativa que se pasa al contenedor requiere una interfaz diferente, se debe realizar una Marshal.QueryInterface()
interfaz para esa interfaz.
var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
Paso 3: Implementar CreateObject()
Construir un contenedor de objetos nativos tiene más opciones de implementación y un gran matiz más que construir un contenedor de objetos administrados. La primera pregunta que se debe abordar es cómo permisiva será la ComWrappers
subclase en admitir tipos COM. Para admitir todos los tipos COM, lo que es posible, deberá escribir una cantidad considerable de código o emplear algunos usos inteligentes de Reflection.Emit
. En este tutorial, solo admitirá instancias COM que implementan IDemoGetType
y IDemoStoreType
. Dado que sabe que hay un conjunto finito y ha restringido que cualquier instancia COM proporcionada debe implementar ambas interfaces, podría proporcionar un único contenedor definido estáticamente; sin embargo, los casos dinámicos son lo suficientemente comunes en COM que exploraremos ambas opciones.
Contenedor de objetos nativos estáticos
Vamos a echar un vistazo a la implementación estática antes. El contenedor de objetos nativos estático implica definir un tipo administrado que implementa las interfaces de .NET y puede reenviar las llamadas en el tipo administrado a la instancia COM. A continuación se muestra un esquema aproximado del contenedor 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 una instancia de esta clase y proporcionarla como contenedor, debe definir alguna directiva. Si este tipo se usa como contenedor, parecería que, puesto que implementa ambas interfaces, la instancia COM subyacente también debe implementar ambas interfaces. Dado que estás adoptando esta directiva, deberás confirmar esto mediante llamadas a Marshal.QueryInterface()
en la instancia 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
};
Contenedor de objetos dinámicos nativos
Los contenedores dinámicos son más flexibles porque proporcionan una manera de consultar los tipos en tiempo de ejecución en lugar de estáticamente. Para proporcionar este soporte técnico, se pueden IDynamicInterfaceCastable
encontrar más detalles aquí. Observa que DemoNativeDynamicWrapper
solo implementa esta interfaz. La funcionalidad que proporciona la interfaz es una oportunidad para determinar qué tipo se admite en tiempo de ejecución. El origen de este tutorial realiza una comprobación estática durante la creación, pero es simplemente para el uso compartido de código, ya que la comprobación se podría aplazar hasta que se realiza una llamada 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();
}
Echemos un vistazo a una de las interfaces que DemoNativeDynamicWrapper
admitirán dinámicamente. El código siguiente proporciona la implementación del uso de IDemoStoreType
la característica de métodos de interfaz predeterminados.
[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);
}
}
Hay dos aspectos importantes que hay que tener en cuenta en este ejemplo:
- El atributo
DynamicInterfaceCastableImplementationAttribute
. Este atributo es necesario en cualquier tipo que se devuelva desde un métodoIDynamicInterfaceCastable
. Tiene la ventaja adicional de facilitar el recorte de IL, lo que significa que los escenarios de AOT son más confiables. - La conversión a
DemoNativeDynamicWrapper
. Esto forma parte de la naturaleza dinámica deIDynamicInterfaceCastable
. El tipo que se devuelve deIDynamicInterfaceCastable.GetInterfaceImplementation()
se usa para «tapar» el tipo que implementaIDynamicInterfaceCastable
. El gist aquí es elthis
puntero no es lo que pretende ser porque estamos permitiendo un caso deDemoNativeDynamicWrapper
aIDemoStoreTypeNativeWrapper
.
Reenvío de llamadas a la instancia COM
Independientemente del contenedor de objetos nativos que se use, necesita la capacidad de invocar funciones en una instancia COM. La implementación de IDemoStoreTypeNativeWrapper.StoreString()
puede servir como ejemplo de emplear unmanaged
punteros de función de 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);
}
}
Examinemos la desreferenciación de la instancia COM para acceder a su implementación vtable. La ABI COM define que el primer puntero de un objeto es a la tabla virtual del tipo y, desde allí, se puede acceder a la ranura deseada. Supongamos que la dirección del objeto COM es 0x10000
. El primer valor de tamaño de puntero debe ser la dirección de la tabla virtual, en este ejemplo 0x20000
. Una vez que estés en la tabla virtual, busca la cuarta ranura (índice 3 en la indexación basada en cero) para acceder a la StoreString()
implementación.
COM instance
0x10000 0x20000
VTable for IDemoStoreType
0x20000 <Address of QueryInterface>
0x20008 <Address of AddRef>
0x20010 <Address of Release>
0x20018 <Address of StoreString>
Después, tener el puntero de función te permite enviar a esa función miembro en ese objeto pasando la instancia del objeto como primer parámetro. Este patrón debe ser familiar en función de las definiciones de función de la implementación del contenedor de objetos administrados.
Una vez implementado el método CreateObject()
, la subclase ComWrappers
podrá generar contenedores de objetos nativos para instancias COM que implementan IDemoGetType
y IDemoStoreType
.
IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);
Paso 4: Controlar los detalles de duración del contenedor de objetos nativos
Las implementaciones ComputeVtables()
y CreateObject()
trataron algunos detalles de duración del contenedor, pero hay otras consideraciones. Aunque esto puede ser un paso corto, también puede aumentar significativamente la complejidad del ComWrappers
diseño.
A diferencia del contenedor de objetos administrados, que se controla mediante llamadas a sus métodos AddRef()
y Release()
, la duración de un contenedor de objetos nativos no se controla de forma determinista mediante el GC. La pregunta aquí es, ¿cuándo llama a Release()
el contenedor de objetos nativos en el IntPtr
que representa la instancia COM? Hay dos cubos generales:
El finalizador del contenedor de objetos nativos es responsable de llamar al método de
Release()
la instancia COM. Esta es la única vez que es seguro llamar a este método. En este momento, el GC determina correctamente que no hay otras referencias al contenedor de objetos nativos en el entorno de ejecución de .NET. Puede haber complejidad aquí si usted está admitiendo correctamente COM Apartamentos; para obtener más información, consulte la sección Consideraciones adicionales.El contenedor de objetos nativos
IDisposable
implementa y llama aRelease()
enDispose()
.
Nota
El IDisposable
patrón solo se debe admitir si, durante la CreateObject()
llamada, se pasó la CreateObjectFlags.UniqueInstance
marca. Si no se sigue este requisito, es posible reutilizar los contenedores de objetos nativos eliminados después de eliminarse.
Uso de la subclase ComWrappers
Ahora tienes una subclase ComWrappers
que se puede probar. Para evitar crear una biblioteca nativa que devuelva una instancia COM que implemente IDemoGetType
y IDemoStoreType
, usarás el contenedor de objetos administrados y lo tratará como una instancia COM; esto debe ser posible para pasarlo COM de todos modos.
Vamos a crear primero un contenedor de objetos administrados. Crea una instancia de DemoImpl
y muestra tu estado de cadena actual.
var demo = new DemoImpl();
string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");
Ahora puedes crear una instancia de DemoComWrappers
y un contenedor de objetos administrados que, a continuación, puede pasar a un entorno COM.
var cw = new DemoComWrappers();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
En lugar de pasar el Managed Object Wrapper a un entorno COM, imagina que acabas de recibir esta instancia COM, por lo que crearás un Native Object Wrapper para ella.
var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);
Con el contenedor de objetos nativos, debes poder convertirlo en una de las interfaces deseadas y usarlo como un objeto administrado normal. Puedes examinar la DemoImpl
instancia y observar el impacto de las operaciones en el contenedor de objetos nativos que encapsula un contenedor de objetos administrados que a su vez encapsula la instancia administrada.
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}");
Dado que la ComWrapper
subclase se diseñó para admitir CreateObjectFlags.UniqueInstance
, puedes limpiar el contenedor de objetos nativos inmediatamente en lugar de esperar a que se produzca un GC.
(rcw as IDisposable)?.Dispose();
Activación COM con ComWrappers
Normalmente, la creación de objetos COM se realiza a través de la activación COM: un escenario complejo fuera del ámbito de este documento. Para proporcionar un patrón conceptual que se debe seguir, presentamos la CoCreateInstance()
API, se usa para la activación COM y se muestra cómo se puede usar con ComWrappers
.
Supongamos que tiene el siguiente código de C# en la aplicación. En el ejemplo siguiente se usa CoCreateInstance()
para activar una clase COM y el sistema de interoperabilidad COM integrado para serializar la instancia COM en la interfaz adecuada. Ten en cuenta que el uso de typeof(I).GUID
se limita a una aserción y es un caso de uso de reflexión que puede afectar si el código es descriptivo 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);
La conversión de lo anterior para su uso ComWrappers
implica quitar el MarshalAs(UnmanagedType.Interface)
del objeto CoCreateInstance()
P/Invoke y realizar la serialización 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);
También es posible abstraer funciones de estilo de fábrica como ActivateClass<I>
, por ejemplo, incluir la lógica de activación en el constructor de clase para un contenedor de objetos nativos. El constructor puede usar la ComWrappers.GetOrRegisterObjectForComInstance()
API para asociar el objeto administrado recién construido a la instancia COM activada.
Consideraciones adicionales
La compilación AOT nativa: la compilación anticipada (AOT) proporciona un costo de inicio mejorado, ya que se evita la compilación JIT. La eliminación de la necesidad de compilación JIT también suele ser necesaria en algunas plataformas. Admitir AOT era un objetivo de la API ComWrappers
, pero cualquier implementación de contenedor debe tener cuidado de no introducir accidentalmente casos en los que AOT se desglosa, como el uso de la reflexión. La propiedad Type.GUID
es un ejemplo de dónde se usa la reflexión, pero de forma no obvia. La propiedad Type.GUID
usa la reflexión para inspeccionar los atributos del tipo y, después, potencialmente el nombre del tipo y el ensamblado contenedor para generar su valor.
Generación de código fuente: la mayoría del código necesario para la interoperabilidad COM y es probable que una implementación ComWrappers
se genere automáticamente mediante algunas herramientas. El origen de ambos tipos de contenedores se podría generar según las definiciones COM adecuadas: por ejemplo, Biblioteca de tipos (TLB), IDL o ensamblado de interoperabilidad primario (PIA).
Registro global: dado que la API ComWrappers
se diseñó como una nueva fase de interoperabilidad COM, era necesario tener alguna manera de integrarse parcialmente con el sistema existente. Hay métodos estáticos que afectan globalmente a la API ComWrappers
que permiten el registro de una instancia global para diversos soporte técnicos. Estos métodos están diseñados para instancias ComWrappers
que esperan proporcionar compatibilidad completa con la interoperabilidad COM en todos los casos, similar al sistema de interoperabilidad COM integrado.
Compatibilidad con el rastreador de referencias: esta compatibilidad se usa principal para escenarios de WinRT y representa un escenario avanzado. Para la mayoría de las implementaciones ComWrapper
, una marca CreateComInterfaceFlags.TrackerSupport
o CreateObjectFlags.TrackerObject
debe iniciar un NotSupportedException. Si deseas habilitar esta compatibilidad, quizás en una plataforma Windows o incluso en una plataforma que no sea Windows, se recomienda encarecidamente hacer referencia a la cadena de herramientas de C#/WinRT.
Además de la duración, el sistema de tipos y las características funcionales que se describen anteriormente, una implementación compatible con COM de ComWrappers
requiere consideraciones adicionales. Para cualquier implementación que se usará en la plataforma Windows, hay las siguientes consideraciones:
Apartamentos: la estructura organizativa de COM para subprocesos se denomina «Apartamentos» y tiene reglas estrictas que se deben seguir para las operaciones estables. En este tutorial no se implementan contenedores de objetos nativos compatibles con apartamentos, pero cualquier implementación lista para producción debe tener en cuenta los apartamentos. Para ello, se recomienda usar la API
RoGetAgileReference
introducida en Windows 8. Para las versiones anteriores a Windows 8, considere la tabla de interfaz global.Seguridad: COM proporciona un modelo de seguridad enriquecido para la activación de clases y el permiso proxy.