Compartir a través de


Contenedor CCW

Cuando un cliente COM llama a un objeto .NET, el entorno de ejecución de lenguaje común crea el objeto administrado y un contenedor invocable por COM (CCW) para el objeto. No se puede hacer referencia directamente a un objeto .NET; los clientes COM usan el CCW como el proxy para el objeto administrado.

El tiempo de ejecución crea exactamente un CCW para un objeto administrado, independientemente del número de clientes COM que solicitan sus servicios. Como se muestra en la siguiente ilustración, varios clientes COM pueden guardar una referencia al CCW que expone la interfaz INew. A su vez, el CCW contiene una única referencia al objeto administrado que implementa la interfaz y se recolectan los elementos no utilizados. Los clientes COM y .NET pueden realizar solicitudes en el mismo objeto administrado simultáneamente.

Varios clientes COM que guardan una referencia al CCW que expone la interfaz INew.

Los contenedores CCW no están visibles para otras clases en ejecución en el runtime de .NET. Su objetivo principal es serializar llamadas entre código administrado y no administrado; sin embargo, los CCW también pueden administrar la identidad y la duración de los objetos administrados que contienen.

Identidad de objeto

El motor en tiempo de ejecución asigna memoria para el objeto .NET desde su montón tras haber eliminado la memoria no utilizada, lo que permite que el motor en tiempo de ejecución mueva el objeto en la memoria como sea necesario. En contraposición, el motor en tiempo de ejecución asigna al CCW memoria de un montón en el que no se ha eliminado la memoria no utilizada, con lo que los clientes COM pueden hacer referencia al contenedor directamente.

Duración del objeto

A diferencia del cliente .NET que contiene, las referencias del CCW se cuentan siguiendo la manera habitual de COM. Cuando el número de referencias del CCW llega a cero, el contenedor libera su referencia en el objeto administrado. Los objetos administrados a los que ya no quedan referencias se recogen en el siguiente ciclo de recolección de memoria no utilizada.

Simulación de interfaces COM

CCW expone todos los tipos de datos, interfaces visibles para COM públicas y valores devueltos a los clientes COM de manera coherente con los requisitos de COM de interacción basada en la interfaz. Para un cliente COM, invocar métodos en un objeto .NET es idéntico a invocar métodos en un objeto COM.

Para crear este enfoque sin problemas, el CCW fabrica interfaces COM tradicionales, como IUnknown e IDispatch. Como se muestra en la ilustración siguiente, el CCW mantiene una sola referencia en el objeto .NET que contiene. Tanto el cliente COM como el objeto .NET interactúan entre sí a través de la construcción de proxies y stubs del CCW.

Diagrama que muestra cómo CCW fabrica interfaces COM.

Además de exponer las interfaces que implementa explícitamente una clase en el entorno administrado, el entorno de ejecución de .NET proporciona implementaciones de las interfaces COM enumeradas en la tabla siguiente en nombre del objeto. Una clase .NET puede invalidar el comportamiento predeterminado proporcionando su propia implementación de estas interfaces. Sin embargo, el tiempo de ejecución siempre proporciona la implementación de las interfaces IUnknown e IDispatch .

Interfaz Descripción
IDispatch Proporciona un mecanismo para el enlace en tiempo de ejecución al tipo.
IErrorInfo Proporciona una descripción textual del error, su origen, un archivo de Ayuda, un contexto de Ayuda y el GUID de la interfaz que definió el error (siempre GUID_NULL para las clases de .NET).
IProvideClassInfo Permite a los clientes COM obtener acceso a la interfaz ITypeInfo implementada por una clase administrada. Devuelve COR_E_NOTSUPPORTED en .NET Core para los tipos no importados desde COM.
ISupportErrorInfo Permite a un cliente COM determinar si el objeto administrado admite la interfaz IErrorInfo . Si es así, permite al cliente obtener un puntero al objeto de excepción más reciente. Todos los tipos administrados admiten la interfaz IErrorInfo .
ITypeInfo (solo .NET Framework) Proporciona información de tipo para una clase que es exactamente la misma que la información de tipo generada por Tlbexp.exe.
IUnknown Proporciona la implementación estándar de la interfaz IUnknown con la que el cliente COM administra la duración del CCW y proporciona coerción de tipos.

Una clase administrada también puede proporcionar las interfaces COM descritas en la tabla siguiente.

Interfaz Descripción
Interfaz de clase (_classname) Interfaz, expuesta por el tiempo de ejecución y no definida explícitamente, que expone todas las interfaces públicas, métodos, propiedades y campos que se exponen explícitamente en un objeto administrado.
IConnectionPoint e IConnectionPointContainer Interfaz para objetos que generan eventos basados en delegados (una interfaz para registrar suscriptores de eventos).
IDispatchEx (solo .NET Framework) Interfaz proporcionada por el runtime si la clase implementa IExpando. La interfaz IDispatchEx es una extensión de la interfaz IDispatch que, a diferencia de IDispatch, habilita la enumeración, la adición, la eliminación y la llamada con distinción entre mayúsculas y minúsculas de los miembros.
IEnumVARIANT Interfaz para las clases de tipo de colección, que enumera los objetos de la colección si la clase implementa IEnumerable.

Presentación de la interfaz de clase

La interfaz de clase, que no se define explícitamente en código administrado, es una interfaz que expone todos los métodos públicos, propiedades, campos y eventos que se exponen explícitamente en el objeto .NET. Puede ser una interfaz dual o sólo de envío. La interfaz de clase recibe el nombre de la propia clase .NET, precedida de un carácter de subrayado. Por ejemplo, para la clase Mammal, la interfaz de clase es _Mammal.

En el caso de las clases derivadas, la interfaz de clase también expone todos los métodos, propiedades y campos públicos de la clase base. La clase derivada también expone una interfaz de clase para cada clase base. Por ejemplo, si la clase Mammal extiende la clase MammalSuperclass, que extiende System.Object, el objeto .NET expone a los clientes COM tres interfaces de clase denominadas _Mammal, _MammalSuperclass y _Object.

Por ejemplo, considere la siguiente clase .NET:

' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
    Sub Eat()
    Sub Breathe()
    Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
    public void Eat() {}
    public void Breathe() {}
    public void Sleep() {}
}

El cliente COM puede obtener un puntero a una interfaz de clase denominada _Mammal. En .NET Framework, puede usar la herramienta Exportador de biblioteca de tipos (Tlbexp.exe) para generar una biblioteca de tipos que contenga la definición de interfaz _Mammal . El exportador de la biblioteca de tipos no se admite en .NET Core. Si la Mammal clase implementó una o varias interfaces, las interfaces aparecerían en la coclase.

[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
    [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
        pRetVal);
    [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
        VARIANT_BOOL* pRetVal);
    [id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
    [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x6002000d)] HRESULT Eat();
    [id(0x6002000e)] HRESULT Breathe();
    [id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
    [default] interface _Mammal;
}

La generación de la interfaz de clase es opcional. De manera predeterminada, la interoperabilidad COM genera una interfaz solo de envío para cada clase que se exporta a una biblioteca de tipos. Puede evitar o modificar la creación automática de esta interfaz aplicando el ClassInterfaceAttribute a su clase. Aunque la interfaz de clase puede facilitar la tarea de exponer clases administradas a COM, sus usos son limitados.

Precaución

El uso de la interfaz de clase, en lugar de definir explícitamente su propio, puede complicar el control de versiones futuro de la clase administrada. Lea las instrucciones siguientes antes de usar la interfaz de clase.

Defina una interfaz explícita para que los clientes COM usen en lugar de generar la interfaz de clase.

Dado que la interoperabilidad COM genera automáticamente una interfaz de clase, los cambios posteriores a la versión de la clase pueden modificar el diseño de la interfaz de clase expuesta por Common Language Runtime. Puesto que los clientes COM no están preparados normalmente para controlar cambios en el diseño de una interfaz, se interrumpen si se cambia el diseño de miembro de la clase.

Esta guía refuerza la noción de que las interfaces expuestas a los clientes COM deben permanecer inmutables. Para reducir el riesgo de perjudicar a los clientes COM al reordenar accidentalmente el diseño de la interfaz, aísle todos los cambios en la clase del diseño de la interfaz definiendo las interfaces de manera explícita.

Use ClassInterfaceAttribute para desenganchar la generación automática de la interfaz de clase e implementar una interfaz explícita para la clase, como se muestra en el fragmento de código siguiente:

<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
    Implements IExplicit
    Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
    int IExplicit.M() { return 0; }
}

El valor ClassInterfaceType.None impide que la interfaz de clase se genere cuando los metadatos de clase se exportan a una biblioteca de tipos. En el ejemplo anterior, los clientes COM solo pueden acceder a la LoanApp clase a través de la IExplicit interfaz .

Evitar el almacenamiento en caché de identificadores de envío (DispIds)

El uso de la interfaz de clase es una opción aceptable para los clientes con scripts, los clientes de Microsoft Visual Basic 6.0 o cualquier cliente enlazado en tiempo de ejecución que no almacene en caché los identificadores DispId de los miembros de la interfaz. Los identificadores DispId identifican los miembros de la interfaz para admitir enlaces en tiempo de ejecución.

Para la interfaz de clase, la generación de DispIds se basa en la posición del miembro en la interfaz. Si cambia el orden del miembro y exporta la clase a una biblioteca de tipos, modificará los DispIds generados en la interfaz de clase.

Para evitar que los clientes COM enlazados en tiempo de ejecución se interrumpan al usar la interfaz de clase, aplique ClassInterfaceAttribute con el valor ClassInterfaceType.AutoDispatch. Este valor implementa una interfaz de clase de solo envío, pero omite la descripción de la interfaz de la biblioteca de tipos. Sin una descripción de interfaz, los clientes no pueden almacenar en caché DispIds en tiempo de compilación. Aunque este es el tipo de interfaz predeterminado para la interfaz de clase, puede aplicar explícitamente el valor de atributo.

<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
    Implements IAnother
    Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
    public int M() { return 0; }
}

Para obtener el DispId de un miembro de interfaz en tiempo de ejecución, los clientes COM pueden llamar a IDispatch.GetIdsOfNames. Para invocar un método en la interfaz, pase el DispId devuelto como argumento a IDispatch.Invoke.

Restrinja el uso de la opción de interfaz dual para la interfaz de clase.

Las interfaces duales permiten el enlace en tiempo de diseño y en tiempo de ejecución de los clientes COM con los miembros de interfaz. En tiempo de diseño y durante las pruebas, es posible que le resulte útil establecer la interfaz de clase en dual. Para una clase administrada (y sus clases base) que nunca se modificarán, esta opción también es aceptable. En todos los demás casos, evite establecer la interfaz de clase en dual.

Una interfaz dual generada automáticamente podría ser adecuada en raras ocasiones; sin embargo, con más frecuencia crea complejidad relacionada con la versión. Por ejemplo, los clientes COM que usan la interfaz de clase de una clase derivada pueden interrumpir fácilmente los cambios en la clase base. Cuando un tercero proporciona la clase base, el diseño de la interfaz de clase está fuera del control. Además, a diferencia de una interfaz de solo envío, una interfaz dual (ClassInterfaceType.AutoDual) proporciona una descripción de la interfaz de clase en la biblioteca de tipos exportada. Dicha descripción hace que los clientes enlazados en tiempo de ejecución almacenen en caché los identificadores DispId en tiempo de compilación.

Asegúrese de que todas las notificaciones de eventos COM están enlazadas tardíamente.

De forma predeterminada, la información de tipo COM se inserta directamente en ensamblados administrados, lo que elimina la necesidad de ensamblados de interoperabilidad primarios (PIA). Sin embargo, una de las limitaciones de la información de tipo insertada es que no admite la entrega de notificaciones de eventos COM mediante llamadas enlazadas tempranamente vtable, sino que solo admite llamadas IDispatch::Invoke enlazadas tardíamente.

Si la aplicación requiere las llamadas enlazadas tempranamente a los métodos de interfaz de eventos COM, puede establecer la propiedad Incrustar tipos de interoperabilidad en Visual Studio en true o incluir el siguiente elemento en el archivo de proyecto:

<EmbedInteropTypes>True</EmbedInteropTypes>

Consulte también