Compartir a través de


Calificar los tipos de .NET para la interoperación COM

Exponer tipos de .NET a COM

Si piensa exponer tipos en un ensamblado a aplicaciones COM, tenga en cuenta los requisitos de interoperabilidad COM en tiempo de diseño. Los tipos administrados (clase, interfaz, estructura y enumeración) se integran perfectamente con los tipos COM cuando se cumplen las instrucciones siguientes:

  • Las clases deben implementar interfaces explícitamente.

    Aunque la interoperabilidad COM proporciona un mecanismo para generar automáticamente una interfaz que contiene todos los miembros de la clase y los miembros de su clase base, es mucho mejor proporcionar interfaces explícitas. La interfaz generada automáticamente se denomina interfaz de clase. Para obtener instrucciones, consulte Introducción a la interfaz de clase.

    Puede usar Visual Basic, C#y C++ para incorporar definiciones de interfaz en el código, en lugar de tener que usar el lenguaje de definición de interfaz (IDL) o su equivalente. Para más información sobre la sintaxis, consulte la documentación del lenguaje.

  • Los tipos administrados deben ser públicos.

    Solo los tipos públicos de un ensamblado se registran y exportan a la biblioteca de tipos. Como resultado, solo los tipos públicos son visibles para COM.

    Los tipos administrados exponen características a otro código administrado que podría no exponerse a COM. Por ejemplo, los constructores con parámetros, los métodos estáticos y los campos constantes no se exponen a los clientes COM. Además, a medida que el tiempo de ejecución maneja los datos al entrar y salir de un tipo, los datos se pueden copiar o transformar.

  • Los métodos, propiedades, campos y eventos deben ser públicos.

    Los miembros de tipos públicos también deben ser públicos para que sean visibles para COM. Puede restringir la visibilidad de un ensamblado, un tipo público o miembros públicos de un tipo público aplicando el ComVisibleAttribute. De forma predeterminada, todos los tipos públicos y miembros están visibles.

  • Los tipos deben tener un constructor público sin parámetros para activarse desde COM.

    Los tipos públicos administrados son visibles para COM. Sin embargo, sin un constructor público sin parámetros (un constructor sin argumentos), los clientes COM no pueden crear el tipo. Los clientes COM todavía pueden usar el tipo si se activa por algún otro medio.

  • Los tipos no pueden ser abstractos.

    Ni los clientes COM ni los clientes .NET pueden crear tipos abstractos.

Cuando se exporta a COM, la jerarquía de herencia de un tipo administrado se aplana. El control de versiones también difiere entre entornos administrados y no administrados. Los tipos expuestos a COM no tienen las mismas características de control de versiones que otros tipos administrados.

Consumo de tipos COM desde .NET

Si piensa consumir tipos COM de .NET y no quiere usar herramientas como Tlbimp.exe (Importador de biblioteca de tipos), debe seguir estas instrucciones:

  • Las interfaces deben tener ComImportAttribute aplicado.
  • Las interfaces deben tener aplicado GuidAttribute con el identificador de interfaz para la interfaz COM.
  • Las interfaces deben tener InterfaceTypeAttribute aplicado para especificar el tipo de interfaz base de esta interfaz (IUnknown, IDispatcho IInspectable).
    • La opción predeterminada es tener el tipo base de IDispatch y anexar los métodos declarados a la tabla de funciones virtuales esperada para la interfaz.
    • Solo .NET Framework admite la especificación de un tipo base de IInspectable.

Estas directrices proporcionan los requisitos mínimos para escenarios comunes. Existen muchas más opciones de personalización y se describen en Aplicar atributos de interoperabilidad.

Definición de interfaces COM en .NET

Cuando el código .NET intenta llamar a un método en un objeto COM a través de una interfaz con el ComImportAttribute atributo , debe crear una tabla de funciones virtuales (también conocida como vtable o vftable) para formar la definición de .NET de la interfaz para determinar el código nativo al que se debe llamar. Este proceso es complejo. En los ejemplos siguientes se muestran algunos casos sencillos.

Considere una interfaz COM con algunos métodos:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

Para esta interfaz, en la tabla siguiente se describe su diseño de tabla de funciones virtuales:

IComInterface ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

Cada método se agrega a la tabla de funciones virtuales en el orden en que se declaró. El orden concreto se define mediante el compilador de C++, pero para casos simples sin sobrecargas, el orden de declaración define el orden en la tabla.

Declare una interfaz de .NET que corresponda a esta interfaz de la siguiente manera:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(/* The IID for IComInterface */)]
interface IComInterface
{
    void Method();
    void Method2();
}

InterfaceTypeAttribute especifica la interfaz base. Proporciona algunas opciones:

Valor de ComInterfaceType Tipo de interfaz base Comportamiento de los miembros en la interfaz con atributos
InterfaceIsIUnknown IUnknown En primer lugar, la tabla de funciones virtuales contiene los miembros de IUnknown, y a continuación, los miembros de esta interfaz en orden de declaración.
InterfaceIsIDispatch IDispatch Los miembros no se agregan a la tabla de funciones virtuales. Solo son accesibles a través de IDispatch.
InterfaceIsDual IDispatch En primer lugar, la tabla de funciones virtuales contiene los miembros de IDispatch, y a continuación, los miembros de esta interfaz en orden de declaración.
InterfaceIsIInspectable IInspectable En primer lugar, la tabla de funciones virtuales contiene los miembros de IInspectable, y a continuación, los miembros de esta interfaz en orden de declaración. Solo se admite en .NET Framework.

Herencia de interfaz COM y .NET

El sistema de interoperabilidad COM que usa el ComImportAttribute no interactúa con la herencia de interfaz, por lo que puede provocar un comportamiento inesperado a menos que se tomen algunas medidas de mitigación.

El generador de origen COM que utiliza el atributo System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute interactúa con la herencia de interfaz, por lo que se comporta más como se espera.

Herencia de interfaz COM en C++

En C++, los desarrolladores pueden declarar interfaces COM que derivan de otras interfaces COM de la siguiente manera:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

struct IComInterface2 : public IComInterface
{
    STDMETHOD(Method3)() = 0;
};

Este estilo de declaración se usa regularmente como mecanismo para agregar métodos a objetos COM sin cambiar las interfaces existentes, lo que sería un cambio importante. Este mecanismo de herencia da como resultado los siguientes diseños de tabla de funciones virtuales:

IComInterface ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Como resultado, es fácil llamar a un método definido en IComInterface desde un IComInterface2*. En concreto, llamar a un método en una interfaz base no requiere una llamada para QueryInterface obtener un puntero a la interfaz base. Además, C++ permite una conversión implícita de IComInterface2* a IComInterface*, que está bien definida y le permite evitar llamar a QueryInterface nuevamente. Como resultado, en C o C++, nunca tendrá que llamar QueryInterface para llegar al tipo base si no quiere, lo que puede permitir algunas mejoras de rendimiento.

Nota:

Las interfaces de WinRT no siguen este modelo de herencia. Se definen para seguir el mismo modelo que el modelo de interoperabilidad COM basado en [ComImport] de .NET.

Herencia de interfaz con ComImportAttribute

En .NET, el código de C# que parece la herencia de interfaz no es realmente herencia de interfaz. Observe el código siguiente:

interface I
{
    void Method1();
}
interface J : I
{
    void Method2();
}

Este código no dice, "J implementa I". En realidad, el código dice: "cualquier tipo que implemente J también debe implementar I". Esta diferencia conduce a la decisión fundamental de diseño que hace que la herencia de interfaz en la interoperabilidad basada en ComImportAttribute sea poco ergonómica. Las interfaces siempre se consideran por sí solas; la lista de interfaces base de una interfaz no tiene ningún impacto en los cálculos para determinar una tabla de funciones virtuales para una interfaz .NET determinada.

Como resultado, el equivalente natural del ejemplo anterior de la interfaz COM de C++ conduce a un diseño de tabla de función virtual diferente.

Código de C#:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

Diseños de tabla de funciones virtuales:

IComInterface ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

Dado que estas tablas de funciones virtuales difieren del ejemplo de C++, esto provocará problemas graves en tiempo de ejecución. La definición correcta de estas interfaces en .NET con ComImportAttribute es la siguiente:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    new void Method();
    new void Method2();
    void Method3();
}

En el nivel de metadatos, IComInterface2 no implementa IComInterface , sino que solo especifica que los implementadores de IComInterface2 también deben implementar IComInterface. Por lo tanto, cada método de los tipos de interfaz base debe volver a declararse.

Herencia de interfaz con GeneratedComInterfaceAttribute (.NET 8 y versiones posteriores)

El generador de código fuente COM, desencadenado por GeneratedComInterfaceAttribute, implementa la herencia de la interfaz de C# como herencia de interfaz COM, por lo que las tablas de funciones virtuales se disponen según lo previsto. Si toma el ejemplo anterior, la definición correcta de estas interfaces en .NET con System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute es la siguiente:

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

No es necesario ni se debe volver a declarar los métodos de las interfaces base. En la tabla siguiente se describen las tablas de funciones virtuales resultantes:

IComInterface ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 ranura de tabla de funciones virtuales Nombre del método
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Como puede ver, estas tablas coinciden con el ejemplo de C++, por lo que estas interfaces funcionarán correctamente.

Consulte también