Compartir por


Tutorial: Uso de la ComWrappers API

En este tutorial, aprenderá a crear una subclase del tipo ComWrappers para proporcionar una solución de interoperabilidad COM optimizada y amigable con AOT. Antes de comenzar este tutorial, debe estar familiarizado con COM, su arquitectura y las soluciones de interoperabilidad COM existentes.

En este tutorial, implementará las siguientes definiciones de interfaz. Estas interfaces y sus implementaciones mostrarán lo siguiente:

  • Marshalización y desmarshalizació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 código para crear automáticamente una ComWrappers implementación de API. Para obtener más información, consulte ComWrappers generación de origen.

Definiciones de C#

interface IDemoGetType
{
    string? GetString();
}

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

Definiciones de C++ de Win32

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 sobre el ComWrappers diseño

La ComWrappers API se diseñó para proporcionar la interacción mínima necesaria para realizar 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:

Estas eficiencias se logran exigiendo que la creación y adquisición de envoltorios pasen por la ComWrappers API.

Dado que la ComWrappers API tiene tan pocas responsabilidades, significa que la mayoría del trabajo de interoperabilidad debe manejarlo el 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 código que se basa en ComWrappers para proporcionar compatibilidad de interoperabilidad con WinRT.

Implementación de una ComWrappers subclase

Proporcionar una ComWrappers subclase significa ofrecer suficiente información al entorno de ejecución de .NET para crear y registrar contenedores que permitan proyectar objetos administrados en COM y objetos COM 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 de 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: Definir 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 método ReleaseObjects() es utilizado por el tiempo de ejecución para hacer una solicitud para que la colección de envoltorios proporcionada sea "liberada" del objeto nativo subyacente. En la mayoría de los casos, el ReleaseObjects() método simplemente puede lanzar NotImplementedException, ya que solo se llama en un escenario avanzado que implica el framework 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 ComputeVtables() método, decida qué tipos administrados desea admitir. En este tutorial, admitiremos las dos interfaces definidas anteriormente (IDemoGetType y ) y IDemoStoreTypeun 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(), deberá también determinar lo que desea 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 estamos proyectando desde el lado de .NET (es decir, IDemoGetType y IDemoStoreType).

En este tutorial no se implementará ReleaseObjects() .

Paso 2: Implementar ComputeVtables()

Comencemos con el Managed Object Wrapper: estos wrappers son más fáciles. Creará una tabla de métodos virtuales o una tabla virtual para cada interfaz para proyectarlas en el entorno COM. En este tutorial, definirá 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 IUnknown tipo tiene tres métodos definidos en el orden siguiente: QueryInterface(), AddRef()y Release(). Después de los IUnknown métodos, vienen los métodos específicos de interfaz. Por ejemplo, considere IDemoGetType y IDemoStoreType. Conceptualmente, las tablas virtuales (vtables) 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á que el entorno de ejecución controle esa parte. Puede obtener la IUnknown implementación mediante el ComWrappers.GetIUnknownImpl() método .

Puede parecer que ha implementado todos los métodos, pero desafortunadamente, solo las IUnknown funciones son consumibles en una tabla virtual 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. Puede 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() : recuerde de la ABI COM que el primer argumento es la propia instancia.

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

La implementación del contenedor de IDemoGetType.GetString() debe constar de lógica de codificación para comunicación y, a continuación, una transferencia al objeto administrado que está siendo envuelto. Todo el estado de envío está incluido en el argumento proporcionado _this . El _this argumento será realmente de tipo ComInterfaceDispatch*. Este tipo representa una estructura de bajo nivel con un único campo, Vtable, que se analizará más adelante. Los detalles adicionales de este tipo y su diseño son un detalle de implementación del entorno de ejecución y no se debe depender de ellos. Para recuperar la instancia administrada de una ComInterfaceDispatch* instancia, use el código siguiente:

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

Ahora que tiene un método de C# que se puede insertar en una tabla virtual, puede construir la tabla virtual. Tenga en cuenta el uso de RuntimeHelpers.AllocateTypeAssociatedMemory() para asignar memoria de manera que sea compatible 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 ComputeVtables() vtables es la primera parte de la implementación. También debe crear definiciones COM completas para los tipos que planea admitir; piense en DemoImpl y qué partes de él deben ser utilizables desde COM. Con las tablas virtuales construidas, ahora puede crear una serie de ComInterfaceEntry instancias que representan 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 constructor o en un static 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 ComputeVtables() método , la ComWrappers subclase podrá generar contenedores de objetos administrados para instancias de DemoImpl. Tenga en cuenta que el wrapper de objeto gestionado 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 operación de Marshal.QueryInterface() 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 mucho más matices 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ás instancias COM que implementen ambos 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.

Envoltorio de Objeto Nativo Estático

Echemos un vistazo primero a la implementación estática. 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 presenta 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 va a adoptar esta directiva, deberá confirmar esto a través de 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
};

Envoltorio de objetos nativos dinámicos

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, utilizará IDynamicInterfaceCastable. Observe 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 esto es simplemente para el uso compartido de código, ya que la comprobación se puede 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 siguiente código proporciona la implementación de IDemoStoreType utilizando 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 debe tener en cuenta en este ejemplo:

  1. Atributo DynamicInterfaceCastableImplementationAttribute . Este atributo es necesario en cualquier tipo que se devuelva desde un IDynamicInterfaceCastable método . Tiene la ventaja adicional de facilitar el recorte de IL, lo que significa que los escenarios de AOT son más confiables.
  2. Conversión a DemoNativeDynamicWrapper. Esto forma parte de la naturaleza dinámica de IDynamicInterfaceCastable. El tipo que se devuelve de IDynamicInterfaceCastable.GetInterfaceImplementation() se usa para cubrir el tipo que implementa IDynamicInterfaceCastable. La esencia aquí es que el puntero this no es lo que pretende ser porque estamos permitiendo un caso de DemoNativeDynamicWrapper a IDemoStoreTypeNativeWrapper.

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 uso unmanaged de 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);
    }
}

Vamos a examinar la desreferencia de la instancia COM para acceder a su implementación de vtable. La especificación ABI de COM define que el primer puntero de un objeto es a la vtable 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é en la tabla virtual, busque la cuarta ranura (índice 3 en 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 permite acceder a esa función miembro de ese objeto pasando la instancia del objeto como primer parámetro. Este patrón debería ser familiar a partir de las definiciones de función de la implementación del Managed Object Wrapper.

Una vez implementado el CreateObject() método, la ComWrappers subclase podrá generar contenedores de objetos nativos para instancias COM que implementen los 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 envoltorio de objetos nativos.

Las implementaciones de ComputeVtables() y CreateObject() cubrieron algunos detalles de duración del envoltorio, 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 AddRef() métodos y Release() , la duración de un contenedor de objetos nativos no se controla de forma determinista mediante la GC. La pregunta aquí es, ¿cuándo llama Release() el contenedor de objetos nativos en la IntPtr que representa la instancia COM? Hay dos categorías generales:

  1. El finalizador del contenedor de objetos nativos es responsable de llamar al método Release() de la instancia COM. Esta es la única ocasión en 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. Aquí puede haber complejidad si está soportando correctamente COM Apartments; para obtener más información, consulte la sección Consideraciones adicionales.

  2. El contenedor de objetos nativos implementa IDisposable y llama a Release() en Dispose().

Nota:

El IDisposable patrón debería admitirse solo si, durante la CreateObject() llamada, se pasó la CreateObjectFlags.UniqueInstance bandera. Si no se sigue este requisito, es posible reutilizar los envoltorios de objetos nativos después de haberse eliminado.

Uso de la ComWrappers subclase

Ahora tiene una ComWrappers subclase que puede ser puesta a prueba. Para evitar crear una biblioteca nativa que devuelva una instancia de COM que implemente IDemoGetType y IDemoStoreType, utilizará el Managed Object Wrapper y lo tratará como una instancia COM. Esto es necesario para poder transmitirlo como COM de todos modos.

Vamos a crear primero un envoltorio para objetos gestionados. Cree una instancia de DemoImpl y muestre su estado de la cadena actual.

var demo = new DemoImpl();

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

Ahora puede 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 contenedor de objetos administrados a un entorno COM, imagínese que acaba de recibir esta instancia COM, así que va a crear un contenedor de objetos nativos para ella.

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

Con el envoltorio de objetos nativos, podrá convertirlo en una de las interfaces deseadas y usarlo como un objeto administrado normal. Puede 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, puede 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 gestionar la instancia COM en la interfaz adecuada. Tenga 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 influir en la compatibilidad del código con 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 mencionado anteriormente para usar ComWrappers implica eliminar el MarshalAs(UnmanagedType.Interface) del CoCreateInstance() P/Invoke y realizar la marshalizació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> al incluir la lógica de activación en el constructor de clase para un envoltorio 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 menor costo de inicio al evitar 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 ComWrappers API, pero cualquier implementación de envoltorio debe tener cuidado de no introducir accidentalmente casos en los que AOT se rompe, como el uso de reflexión. La Type.GUID propiedad es un ejemplo de dónde se usa la reflexión, pero de forma no obvia. La Type.GUID propiedad utiliza la reflexión para inspeccionar los atributos del tipo y luego potencialmente el nombre y el ensamblado contenedor del tipo 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 pueda generarse automáticamente mediante algunas herramientas. El origen de ambos tipos de envoltorios podría generarse dadas las definiciones COM adecuadas, por ejemplo, Biblioteca de tipos (TLB), IDL o Ensamblado primario de interoperabilidad (PIA).

Registro global : dado que la ComWrappers API 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 con impacto global en la API ComWrappers que permiten el registro de una instancia global para varios tipos de soporte. Estos métodos están diseñados para ComWrappers instancias que esperan proporcionar soporte completo de interoperabilidad COM en todos los casos, similar al sistema integrado de interoperabilidad COM.

Compatibilidad con el rastreador de referencias : esta compatibilidad se usa principal para escenarios de WinRT y representa un escenario avanzado. Para la mayoría ComWrapper de las implementaciones, una CreateComInterfaceFlags.TrackerSupport flag o CreateObjectFlags.TrackerObject debería lanzar un NotSupportedException. Si quiere habilitar esta compatibilidad, quizás en una plataforma Windows o incluso en una plataforma que no sea Windows, se recomienda 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 hilos se denomina "apartamentos" y tiene reglas estrictas que deben seguirse para un funcionamiento estable. 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 RoGetAgileReference API introducida en Windows 8. En el caso de 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.