Problemas de subprocesos del servidor de In-Process

Warning

Marshaling entre apartamentos desde código .NET: Cuando una aplicación .NET crea un objeto COM en proceso cuyo ThreadingModel no coincide con el apartamento de llamada, COM crea el objeto en un apartamento diferente y devuelve un proxy. La llamada a métodos a través de este proxy conlleva una sobrecarga de marshaling y puede provocar interbloqueos si el hilo del apartamento de destino está bloqueado.

Patrón de error común:

// A .NET console app defaults to MTA (no explicit CoInitializeEx).
// Creating an Apartment-threaded COM object forces COM to spin up
// a hidden STA thread. If that STA thread has no message pump,
// calls that require marshaling back to it will hang.

// Fix: If your COM object requires STA, mark Main with [STAThread]:
[STAThread]
static void Main(string[] args)
{
    var comObj = new MyCOMObject(); // Created in the STA — no proxy needed
}

Prácticas recomendadas para los consumidores .NET de servidores COM en proceso:

  • Compruebe el valor de registro del objeto ThreadingModel en HKCR\CLSID\{...}\InprocServer32
  • Utiliza [STAThread] en tu punto de entrada si utilizas objetos con subprocesos por apartamento (WinForms/WPF lo hacen automáticamente).
  • Nunca llames a Thread.Join() ni a Task.Wait() en un subproceso STA; utiliza await o envía mensajes con Dispatcher.PushFrame().

Un servidor en proceso no llama a CoInitialize, CoInitializeEx ni OleInitialize para marcar su modelo de subprocesos. Para los objetos compatibles basados en DLL o en proceso con reconocimiento de subprocesos, debe establecer el modelo de subprocesos en el Registro. El modelo predeterminado cuando no se especifica un modelo de subprocesos es un subproceso por proceso. Para especificar un modelo, agregue el valor threadingModel a la clave InprocServer32 en el Registro.

Los archivos DLL que admiten la creación de instancias de un objeto de clase deben implementar y exportar las funciones DllGetClassObject y DllCanUnloadNow. Cuando un cliente quiere una instancia de la clase que admite el archivo DLL, una llamada a CoGetClassObject (directamente o a través de una llamada a CoCreateInstance) llama a DllGetClassObject para obtener un puntero a su objeto de clase cuando el objeto se implementa en un archivo DLL. DllGetClassObject debería, por tanto, ser capaz de devolver varios objetos de clase o un único objeto seguro para subprocesos (esencialmente, usando InterlockedIncrement/InterlockedDecrement en sus contadores internos de referencias).

Como su nombre implica, se llama a DllCanUnloadNow para determinar si el archivo DLL que lo implementa está en uso, lo que permite al autor de la llamada descargarlo de forma segura si no lo está. Las llamadas a CoFreeUnusedLibraries desde cualquier subproceso siempre se enrutan a través del subproceso principal del contenedor para llamar a DllCanUnloadNow.

Al igual que otros servidores, los servidores en proceso pueden ser subproceso único, de contenedor de subprocesos o libre de subproceso. Cualquier cliente OLE puede usar estos servidores, independientemente del modelo de subprocesos usado por ese cliente.

Se permiten todas las combinaciones de interoperabilidad del modelo de subprocesos entre clientes y objetos dentro del proceso. La interacción entre un cliente y un objeto en proceso que usan diferentes modelos de subprocesos es exactamente igual que la interacción entre los clientes y los servidores fuera de proceso. Para un servidor en proceso, cuando el modelo de subprocesos del cliente y el servidor en proceso difieren, COM debe interponerse entre el cliente y el objeto.

Cuando varios subprocesos de un cliente llaman simultáneamente a un objeto en proceso que admite el modelo de un solo subproceso, COM no puede permitir que los subprocesos de cliente accedan directamente a la interfaz del objeto (el objeto no se diseñó para dicho acceso). En su lugar, COM debe asegurarse de que las llamadas se sincronizan y solo las realiza el subproceso de cliente que creó el objeto. Por lo tanto, COM crea el objeto en el apartamento principal del cliente y requiere que todos los demás apartamentos de cliente accedan al objeto mediante servidores proxy.

Cuando un contenedor libre de subproceso (modelo de contenedor multiproceso) en un cliente crea un servidor en proceso con contenedor de subproceso, COM pone en marcha un subproceso "host" de modelo de contenedor subproceso único en el cliente. Este subproceso de host creará el objeto y el puntero de interfaz se serializará de nuevo al contenedor libre de subproceso del cliente. Del mismo modo, cuando un contenedor subproceso único en un cliente de modelo de contenedor crea un servidor en proceso libre de subproceso, COM pone en marcha un subproceso de host libre de subproceso (contenedor multiproceso en el que se creará el objeto y, a continuación, se serializará de nuevo en el contenedor subproceso único del cliente).

Note

En general, si diseña una interfaz personalizada en un servidor en proceso, también debe proporcionar el código de serialización para que COM pueda gestionar la interfaz entre los contenedores del cliente.

 

COM ayuda a proteger el acceso a los objetos proporcionados por un archivo DLL subproceso único al exigir acceso desde el mismo contenedor del cliente en el que fueron creados. Además, todos los puntos de entrada de la DLL (como DllGetClassObject y DllCanUnloadNow) y los datos globales siempre deben ser accedidos por el mismo apartamento. COM crea dichos objetos en el apartamento principal del cliente, dando al apartamento principal acceso directo a los punteros del objeto. Las llamadas de los otros contenedores utilizan la serialización entre subprocesos para pasar del proxy al código auxiliar en el contenedor principal y luego al objeto. Esto permite que COM sincronice llamadas al objeto . Las llamadas entre hilos son lentas, por lo que se recomienda reescribir estos servidores para que admitan varios apartamentos.

Al igual que un servidor en subproceso único, desde el mismo contenedor de cliente desde el que se creó debe accederse a un objeto proporcionado por un archivo DLL de modelo de contenedor. Sin embargo, los objetos proporcionados por este servidor se pueden crear en varios apartamentos del cliente, por lo que el servidor debe implementar sus puntos de entrada (como DllGetClassObject y DllCanUnloadNow) para uso multiproceso. Por ejemplo, si dos apartamentos de un cliente intentan crear dos instancias del objeto en proceso simultáneamente, ambos apartamentos pueden llamar a DllGetClassObject simultáneamente. DllCanUnloadNow debe escribirse para que el archivo DLL no se descargue mientras el código sigue ejecutándose en el archivo DLL.

Si el archivo DLL solo proporciona una instancia de la factoría de clases para crear todos los objetos, la implementación de generador de clases también debe diseñarse para uso multiproceso, ya que varios apartamentos de cliente tendrán acceso a él. Si el archivo DLL crea una nueva instancia de la factoría de clases cada vez que se llama a DllGetClassObject, la factoría de clases no tiene por qué ser segura para subprocesos.

Los objetos creados por la factoría de clases no necesitan ser seguros para subprocesos. Una vez creado por un subproceso, siempre se accede al objeto a través de ese subproceso y todas las llamadas al objeto se sincronizan mediante COM. El contenedor del modelo de contenedores de un cliente que crea este objeto obtendrá un puntero directo al objeto. Los apartamentos de cliente que son diferentes del apartamento en el que se creó el objeto deben tener acceso al objeto a través de servidores proxy. Estos proxies se crean cuando el cliente marshaliza la interfaz entre sus apartamentos.

Cuando el valor ThreadingModel de una DLL en proceso se establece en "Both", un objeto proporcionado por esta DLL puede crearse y usarse directamente (sin proxy) en apartamentos cliente de un solo subproceso o de varios subprocesos. Sin embargo, solo se puede utilizar directamente dentro del apartamento en el que se creó. Para entregar el objeto a cualquier otro contenedor, el objeto debe serializarse. El objeto DLL debe implementar su propia sincronización, y varios apartamentos cliente pueden acceder a él simultáneamente.

Para mejorar el rendimiento del acceso multiproceso a los objetos de DLL en proceso, COM proporciona la función CoCreateFreeThreadedMarshaler. Esta función crea un objeto de serialización libre de subprocesos que se puede agregar a un objeto de servidor en proceso. Cuando un contenedor de cliente en el mismo proceso necesita acceso a un objeto de otro contenedor, agregar el serializador libre de subprocesos proporciona al cliente un puntero directo al objeto del servidor, en lugar de a un proxy, cuando el cliente serializa la interfaz del objeto a un contenedor diferente. El cliente no necesita realizar ninguna sincronización. Esto solo funciona dentro del mismo proceso; la serialización estándar se usa para una referencia al objeto que se envía a otro proceso.

Importante

Reemplazo moderno para CoCreateFreeThreadedMarshaler: para el nuevo código destinado a Windows 8.1+, prefiera RoGetAgileReference y la IAgileReference interfaz. Ofrecen una forma más segura de pasar referencias a objetos entre apartamentos sin los riesgos del marshaler de subprocesos libres (que elude por completo la protección de los apartamentos y puede enmascarar errores de subprocesos). En el caso de los objetos winRT, la agilidad es la predeterminada: los objetos winRT admiten IAgileObject automáticamente a menos que opten explícitamente por no participar.

Un objeto proporcionado por una DLL en proceso que solo admite subprocesos libres es un objeto de subprocesos libres. Implementa su propia sincronización y se puede acceder a ella mediante varios subprocesos de cliente al mismo tiempo. Este servidor no marshala interfaces entre subprocesos, por lo que solo puede ser creado y utilizado directamente (sin un proxy) por apartamentos multihilo en un cliente. Los contenedores subproceso único hilo que lo crean acceden a él a través de un proxy.

Acceso a interfaces entre contenedores

Elección del modelo de subprocesos

Contenedores multiproceso

Procesos, subprocesos y apartamentos

Comunicación de un solo hilo y multihilo

Apartamentos de un solo hilo