Descriptions et fonctionnement des modèles de thread OLE

Cet article décrit les modèles de thread OLE.

Version d’origine du produit : Modèles de thread OLE
Numéro de la base de connaissances d’origine : 150777

Résumé

Les objets COM peuvent être utilisés dans plusieurs threads d’un processus. Les termes « Monothreaded Apartment » (STA) et « Multi-threaded Apartment » (MTA) sont utilisés pour créer un framework conceptuel pour décrire la relation entre les objets et les threads, les relations d’accès concurrentiel entre les objets, les moyens par lesquels les appels de méthode sont remis à un objet et les règles de passage de pointeurs d’interface entre threads. Les composants et leurs clients choisissent entre les deux modèles d’appartement suivants actuellement pris en charge par COM :

  1. Monothreaded Apartment model (STA) : un ou plusieurs threads d’un processus utilisent COM et les appels aux objets COM sont synchronisés par COM. Les interfaces sont marshalées entre les threads. Un cas dégénéré du modèle d’appartement monothread, où un seul thread dans un processus donné utilise COM, est appelé modèle monothread. Les précédents ont parfois fait référence au modèle STA simplement comme le « modèle d’appartement ».

  2. Modèle d’appartement multithread (MTA) : un ou plusieurs threads utilisent COM et les appels aux objets COM associés à l’assistant multithread sont effectués directement par tous les threads associés au MTA sans aucune interposition de code système entre l’appelant et l’objet. Étant donné que plusieurs clients simultanés peuvent appeler des objets plus ou moins simultanément (simultanément sur des systèmes multiprocesseurs), les objets doivent synchroniser leur état interne par eux-mêmes. Les interfaces ne sont pas marshalées entre les threads. Les précédents ont parfois appelé ce modèle le « modèle à thread libre ».

  3. Le modèle STA et le modèle MTA peuvent être utilisés dans le même processus. Il s’agit parfois d’un processus de « modèle mixte ».

Le MTA est introduit dans NT 4.0 et est disponible dans Windows 95 avec DCOM95. Le modèle STA existe dans Windows NT 3.51 et Windows 95, ainsi que dans NT 4.0 et Windows 95 avec DCOM95.

Vue d’ensemble

Les modèles de thread dans COM fournissent le mécanisme permettant aux composants qui utilisent différentes architectures de thread de fonctionner ensemble. Ils fournissent également des services de synchronisation aux composants qui en ont besoin. Par exemple, un objet particulier peut être conçu pour être appelé uniquement par un seul thread et ne peut pas synchroniser les appels simultanés des clients. Si un tel objet est appelé simultanément par plusieurs threads, il se bloque ou provoque des erreurs. COM fournit les mécanismes permettant de gérer cette interopérabilité des architectures de thread.

Même les composants prenant en charge les threads ont souvent besoin de services de synchronisation. Par exemple, les composants qui ont une interface utilisateur graphique (GUI), tels que les contrôles OLE/ActiveX, les incorporations actives sur place et les documents ActiveX, nécessitent la synchronisation et la sérialisation des appels COM et des messages de fenêtre. COM fournit ces services de synchronisation afin que ces composants puissent être écrits sans code de synchronisation complexe.

Un « appartement » présente plusieurs aspects connexes. Tout d’abord, il s’agit d’une construction logique pour réfléchir à la concurrence, telle que la relation entre les threads et un ensemble d’objets COM. Deuxièmement, il s’agit d’un ensemble de règles que les programmeurs doivent respecter pour recevoir le comportement d’accès concurrentiel qu’ils attendent de l’environnement COM. Enfin, il s’agit d’un code fourni par le système qui aide les programmeurs à gérer la concurrence des threads en ce qui concerne les objets COM.

Le terme « appartement » provient d’une métaphore dans laquelle un processus est conçu comme une entité discrète, telle qu’un « bâtiment » subdivisé en un ensemble de « locaux » connexes mais différents appelés « appartements ». Un appartement est un « conteneur logique » qui crée une association entre des objets et, dans certains cas, des threads. Les threads ne sont pas des appartements, bien qu’un seul thread puisse être associé logiquement à un appartement dans le modèle STA. Les objets ne sont pas des appartements, bien que chaque objet soit associé à un seul appartement. Mais les appartements sont plus qu’une simple construction logique ; leurs règles décrivent le comportement du système COM. Si les règles des modèles d’appartement ne sont pas suivies, les objets COM ne fonctionnent pas correctement.

Plus de détails

Un single-threaded apartment (STA) est un ensemble d’objets COM associés à un thread particulier. Ces objets sont associés à l’appartement en étant créés par le thread ou, plus précisément, exposés pour la première fois au système COM (généralement par marshaling) sur le thread. UN STA est considéré comme un endroit où un objet ou un proxy « vit ». Si l’objet ou le proxy doit être accessible par un autre appartement (dans le même processus ou un autre processus), son pointeur d’interface doit être marshalé vers l’appartement où un nouveau proxy est créé. Si les règles du modèle d’appartement sont suivies, aucun appel direct d’autres threads dans le même processus n’est autorisé sur cet objet ; qui violerait la règle selon laquelle tous les objets d’un appartement donné s’exécutent sur un seul thread. La règle existe, car la plupart du code s’exécutant dans un STA ne fonctionne pas correctement s’il est exécuté sur des threads supplémentaires.

Le thread associé à un STA doit appeler CoInitialize ou et CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) doit récupérer et distribuer des messages de fenêtre pour que les objets associés reçoivent des appels entrants. COM répartit et synchronise les appels aux objets dans un STA à l’aide de messages de fenêtre, comme décrit plus loin dans cet article.

Le « main STA » est le thread qui appelle CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) en premier au sein d’un processus donné. Le main STA d’un processus doit rester actif jusqu’à ce que tout le travail COM soit terminé, car certains objets in-process sont toujours chargés dans le main STA, comme décrit plus loin dans cet article.

Windows NT 4.0 et DCOM95 introduisent un nouveau type d’appartement appelé multithread apartment (MTA). Un MTA est un ensemble d’objets COM associés à un ensemble de threads dans le processus, de sorte que n’importe quel thread peut appeler directement n’importe quelle implémentation d’objet sans l’interposition du code système. Les pointeurs d’interface vers n’importe quel objet dans le MTA peuvent être passés entre les threads associés au MTA sans avoir à être marshalés. Tous les threads du processus qui appellent CoInitializeEx(NULL, COINIT_MULTITHREADED) sont associés au MTA. Contrairement au STA décrit ci-dessus, les threads d’un MTA n’ont pas besoin de récupérer et de distribuer des messages de fenêtre pour que les objets associés reçoivent des appels entrants. COM ne synchronise pas les appels aux objets dans un MTA. Les objets dans un MTA doivent protéger leur état interne contre la corruption par l’interaction de plusieurs threads simultanés et ils ne peuvent pas faire d’hypothèses sur le contenu de Thread-Local Stockage restant constant entre les différents appels de méthode.

Un processus peut avoir n’importe quel nombre de MTA, mais, au maximum, peut avoir un MTA. Le MTA se compose d’un ou plusieurs threads. Les stas ont un thread chacun. Un thread appartient, au plus, à un seul appartement. Les objets appartiennent à un seul appartement. Les pointeurs d’interface doivent toujours être marshalés entre les appartements (bien que le résultat du marshaling puisse être un pointeur direct plutôt qu’un proxy). Consultez les informations ci-dessous sur CoCreateFreeThreadedMarshaler.

Un processus choisit l’un des modèles de thread fournis par COM. Un processus de modèle STA a un ou plusieurs MTA et n’a pas de MTA. Un processus de modèle MTA a un MTA avec un ou plusieurs threads et n’a pas de stas. Un processus de modèle mixte a un MTA et un nombre quelconque de MTA.

Modèle d’appartement à thread unique

Le thread d’un STA doit appeler CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) et doit récupérer et distribuer des messages de fenêtre, car COM utilise des messages de fenêtre pour synchroniser et distribuer les appels à un objet dans ce modèle. Pour plus d’informations, consultez la section RÉFÉRENCEs ci-dessous.

Serveur qui prend en charge le modèle STA :

Dans le modèle STA, les appels à un objet sont synchronisés par COM de la même manière que les messages de fenêtre publiés dans une fenêtre sont synchronisés. Les appels sont remis à l’aide de messages de fenêtre au thread qui a créé l’objet. Par conséquent, le thread de l’objet doit appeler Get/PeekMessage et DispatchMessage pour recevoir des appels. COM crée une fenêtre masquée associée à chaque STA. Un appel à un objet en dehors du STA est transféré par le runtime COM au thread de l’objet à l’aide d’un message de fenêtre publié dans cette fenêtre masquée. Lorsque le thread associé au STA de l’objet récupère et distribue le message, la procédure de fenêtre pour la fenêtre masquée, également implémentée par COM, le reçoit. La procédure de fenêtre est utilisée par le runtime COM pour « raccorder » le thread associé au STA, car le runtime COM se trouve des deux côtés de l’appel du thread appartenant à COM au thread du STA. Le runtime COM (en cours d’exécution dans le thread de STA) appelle « up » via un stub fourni par COM dans la méthode d’interface correspondante de l’objet. Le chemin d’exécution retourné à partir de l’appel de méthode inverse l’appel « up » ; l’appel retourne au stub et au runtime COM, qui transmet le contrôle au thread d’exécution COM via un message de fenêtre, qui retourne ensuite à l’appelant d’origine via le canal COM.

Lorsque plusieurs clients appellent un objet STA, les appels sont automatiquement mis en file d’attente dans la file d’attente de messages par le mécanisme de transfert de contrôle utilisé dans le STA. L’objet reçoit un appel chaque fois que son STA récupère et distribue des messages. Étant donné que les appels sont synchronisés par COM de cette façon et que les appels sont toujours remis sur le thread unique associé au STA de l’objet, les implémentations d’interface de l’objet n’ont pas besoin d’assurer la synchronisation.

Remarque

L’objet peut être entré de nouveau si une implémentation de méthode d’interface récupère et distribue des messages lors du traitement d’un appel de méthode, ce qui entraîne la remise d’un autre appel à l’objet par le même STA. Cela se produit souvent si un objet STA effectue un appel sortant (inter-appartement/interprocesseur) à l’aide de COM. Cela est identique à la façon dont une procédure de fenêtre peut être réinscrite si elle récupère et distribue des messages lors du traitement d’un message. COM n’empêche pas la nouvelle entrée sur le même thread, mais empêche l’exécution simultanée. Il fournit également un moyen de gérer la réentrance liée à COM. Pour plus d’informations, consultez la section RÉFÉRENCEs ci-dessous. L’objet n’est pas entré de nouveau si les implémentations de méthode n’appellent pas hors de son appartement ou ne récupèrent pas et distribuent des messages.

Responsabilités du client dans le modèle STA :

Le code client s’exécutant dans un processus et/ou un thread qui utilise le modèle STA doit marshaler les interfaces d’un objet entre les appartements à l’aide CoMarshalInterThreadInterfaceInStream de et CoGetInterfaceAndReleaseStream. Par exemple, si Apartment 1 dans le client a un pointeur d’interface et que Apartment 2 nécessite son utilisation, Apartment 1 doit marshaler l’interface à l’aide CoMarshalInterThreadInterfaceInStreamde . L’objet de flux retourné par cette fonction est thread-safe et son pointeur d’interface doit être stocké dans une variable de mémoire directe accessible par Apartment 2. Apartment 2 doit passer cette interface de flux à CoGetInterfaceAndReleaseStream pour démarshaler l’interface sur l’objet sous-jacent et récupérer un pointeur vers un proxy par le biais duquel il peut accéder à l’objet.

L’appartement main d’un processus donné doit rester actif jusqu’à ce que le client ait terminé tous les travaux COM, car certains objets in-process sont chargés dans le main-apartment. (Plus d’informations sont détaillées ci-dessous).

Modèle d’appartement multithread

Un MTA est la collection d’objets créés ou exposés par tous les threads du processus qui ont appelé CoInitializeEx(NULL, COINIT_MULTITHREADED).

Remarque

Les implémentations actuelles de COM permettent à un thread qui n’initialise pas explicitement COM de faire partie du MTA. Un thread qui n’initialise pas COM fait partie du MTA uniquement s’il commence à utiliser COM après qu’au moins un autre thread du processus a appelé CoInitializeEx(NULL, COINIT_MULTITHREADED)précédemment . (Il est même possible que COM lui-même ait initialisé le MTA lorsqu’aucun thread client ne l’a fait explicitement ; par exemple, un thread associé à un appel CoGetClassObject/CoCreateInstance[Ex] STA sur un CLSID marqué « ThreadingModel=Free » et COM crée implicitement un MTA dans lequel l’objet de classe est chargé.) Consultez les informations sur l’interopérabilité du modèle de thread ci-dessous.

Toutefois, il s’agit d’une configuration qui peut entraîner des problèmes, tels que des violations d’accès, dans certaines circonstances. Par conséquent, il est recommandé que chaque thread qui a besoin d’effectuer un travail COM initialise COM en appelant CoInitializeEx , puis, à la fin du travail COM, appelez CoUninitialize. Le coût d’initialisation « inutile » d’un MTA est minime.

Les threads MTA n’ont pas besoin de récupérer et de distribuer des messages, car COM n’utilise pas de messages de fenêtre dans ce modèle pour remettre des appels à un objet.

  • Serveur qui prend en charge le modèle MTA :

    Dans le modèle MTA, les appels à un objet ne sont pas synchronisés par COM. Plusieurs clients peuvent appeler simultanément un objet qui prend en charge ce modèle sur différents threads, et l’objet doit fournir une synchronisation dans ses implémentations d’interface/méthode à l’aide d’objets de synchronisation tels que des événements, des mutex, des sémaphores, etc. Les objets MTA peuvent recevoir des appels simultanés de plusieurs clients hors processus via un pool de threads créés par COM appartenant au processus de l’objet. Les objets MTA peuvent recevoir des appels simultanés de plusieurs clients in-process sur plusieurs threads associés au MTA.

  • Responsabilités du client dans le modèle MTA :

    Le code client s’exécutant dans un processus et/ou un thread qui utilise le modèle MTA n’a pas besoin de marshaler les pointeurs d’interface d’un objet entre lui-même et d’autres threads MTA. Au lieu de cela, un thread MTA peut utiliser un pointeur d’interface obtenu à partir d’un autre thread MTA comme pointeur de mémoire directe. Lorsqu’un thread client effectue un appel à un objet hors processus, il s’interrompt jusqu’à ce que l’appel soit terminé. Les appels peuvent arriver sur des objets associés au MTA, tandis que tous les threads créés par l’application associés au MTA sont bloqués sur les appels sortants. Dans ce cas et en général, les appels entrants sont remis sur des threads fournis par le runtime COM. Les filtres de messages (IMessageFilter) ne sont pas disponibles pour une utilisation dans le modèle MTA.

Modèles à threads mixtes

Un processus qui prend en charge le modèle de thread mixte utilise un MTA et un ou plusieurs MTA. Les pointeurs d’interface doivent être marshalés entre tous les appartements, mais peuvent être utilisés sans marshaling dans le MTA. Les appels aux objets dans un STA sont synchronisés par COM pour s’exécuter sur un seul thread, contrairement aux appels aux objets du MTA. Toutefois, les appels d’un STA à un MTA passent normalement par le code fourni par le système et passent du thread STA à un thread MTA avant d’être remis à l’objet.

Remarque

Pour plus d’informations sur les cas où des pointeurs directs peuvent être utilisés et comment un thread STA peut appeler directement dans un objet associé au MTA et inversement, à partir de plusieurs appartements, consultez la documentation du KIT de développement logiciel (SDK) et CoCreateFreeThreadedMarshaler() la discussion sur cette API ci-dessous.

Choix des modèles de thread

Un composant peut choisir de prendre en charge le modèle STA, le modèle MTA ou une combinaison des deux à l’aide du modèle de thread mixte. Par exemple, un objet qui effectue des E/S étendues peut choisir de prendre en charge MTA pour fournir une réponse maximale aux clients en autorisant les appels d’interface à effectuer pendant la latence d’E/S. Sinon, un objet qui interagit avec l’utilisateur choisit presque toujours de prendre en charge STA pour synchroniser les appels COM entrants avec ses opérations d’interface graphique graphique. La prise en charge du modèle STA est plus facile, car COM assure la synchronisation. La prise en charge du modèle MTA est plus difficile, car l’objet doit implémenter la synchronisation, mais la réponse aux clients est préférable, car la synchronisation est utilisée pour des sections de code plus petites, plutôt que pour l’ensemble de l’appel d’interface fourni par COM.

Le modèle STA est également utilisé par Microsoft Transaction Server (MTS, précédemment nommé « Viper »), et par conséquent, les objets basés sur DLL qui prévoient de s’exécuter dans l’environnement MTS doivent utiliser le modèle STA. Les objets implémentés pour le modèle MTA fonctionnent normalement correctement dans un environnement MTS. Toutefois, elles s’exécutent moins efficacement, car elles utilisent des primitives de synchronisation de thread inutiles.

Marquage du modèle threading pris en charge des serveurs In-Proc

Un thread utilise le modèle MTA s’il appelle CoInitializeEx(NULL, COINIT_MULTITHREADED) ou utilise COM sans l’initialiser. Un thread utilise le modèle STA s’il appelle CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).

Les CoInitialize API fournissent un contrôle d’appartement pour le code client et pour les objets qui sont empaquetés dans. EXE, car le code de démarrage du runtime COM peut initialiser COM de la manière souhaitée.

Toutefois, un serveur COM in-proc (basé sur DLL) n’appelle CoInitialize/CoInitializeEx pas, car ces API auront été appelées au moment du chargement du serveur DLL. Par conséquent, un serveur DLL doit utiliser le Registre pour informer COM du modèle de thread qu’il prend en charge afin que COM puisse s’assurer que le système fonctionne d’une manière compatible avec lui. Une valeur nommée de la clé CLSID\InprocServer32 du composant appelée ThreadingModel est utilisée à cet effet comme suit :

  • ThreadingModel valeur non présente : prend en charge le modèle monothread.
  • ThreadingModel=Apartment: prend en charge le modèle STA.
  • ThreadingModel=Both: prend en charge le modèle STA et MTA.
  • ThreadingModel=Free: prend uniquement en charge MTA.

Remarque

ThreadingModel est une valeur nommée, et non une sous-clé d’InprocServer32 comme indiqué incorrectement dans certaines versions antérieures de la documentation Win32.

Les modèles de thread des serveurs in-proc sont décrits plus loin dans cet article. Si un serveur in-proc fournit de nombreux types d’objets (chacun avec son propre CLSID unique), chaque type peut avoir une valeur différente ThreadingModel . En d’autres termes, le modèle de thread est par CLSID, et non par package de code/DLL. Toutefois, les points d’entrée d’API nécessaires pour « bootstrap » et interroger tous les serveurs in-proc (DLLGetClassObject(), DLLCanUnloadNow()) doivent être thread-safe pour tout serveur in-proc qui prend en charge plusieurs threads (c’est-à-dire une ThreadingModel valeur de Apartment, Both ou Free).

Comme indiqué précédemment, les serveurs hors processus ne se marquent pas eux-mêmes à l’aide de la valeur ThreadingModel. Au lieu de cela, ils utilisent CoInitialize ou CoInitializeEx. Les serveurs basés sur DLL qui s’attendent à s’exécuter hors processus à l’aide de la fonctionnalité de « substitution » de COM (comme le DLLHOST.EXE de substitution fourni par le système) suivent les règles pour les serveurs basés sur DLL ; il n’y a pas de considérations particulières dans ce cas.

Lorsque le client et l’objet utilisent différents modèles de thread

L’interaction entre un client et un objet hors processus est directe même lorsque différents modèles de thread sont utilisés, car le client et l’objet font partie de processus différents et COM est impliqué dans le passage des appels du client à l’objet. Étant donné que COM est interposé entre le client et le serveur, il fournit le code pour l’interopérabilité des modèles de thread. Par exemple, si un objet STA est appelé simultanément par plusieurs clients STA ou MTA, COM synchronise les appels en plaçant les messages de fenêtre correspondants dans la file d’attente de messages du serveur. Le STA de l’objet reçoit un appel chaque fois qu’il récupère et distribue des messages. Toutes les combinaisons d’interopérabilité threading-modèle sont autorisées et entièrement prises en charge entre les clients et les objets hors processus.

L’interaction entre un client et un objet in-proc qui utilise différents modèles de thread est plus compliquée. Bien que le serveur soit in-proc, COM doit s’interposer entre le client et l’objet dans certains cas. Par exemple, un objet in-proc conçu pour prendre en charge le modèle STA peut être appelé simultanément par plusieurs threads d’un client. COM ne peut pas autoriser les threads clients à accéder directement à l’interface de l’objet, car l’objet n’est pas conçu pour un tel accès simultané. Au lieu de cela, COM doit s’assurer que les appels sont synchronisés et effectués uniquement par le thread associé au STA qui « contient » l’objet. Malgré la complexité supplémentaire, toutes les combinaisons d’interopérabilité des modèles de thread sont autorisées entre les clients et les objets in-proc.

Modèles de thread dans des serveurs hors processus (basés sur EXE)

Voici trois catégories de serveurs hors processus, chacun pouvant être utilisé par n’importe quel client COM, quel que soit le modèle de thread utilisé par ce client :

  1. Serveur de modèles STA :

    Le serveur fonctionne com dans un ou plusieurs contrats STA. Les appels entrants sont synchronisés par COM et remis par le thread associé au STA dans lequel l’objet a été créé. Les appels de méthode class-factory sont remis par le thread associé au STA qui a inscrit la fabrique de classes. L’objet et la fabrique de classes n’ont pas besoin d’implémenter la synchronisation. Toutefois, l’implémenteur doit synchroniser l’accès à toutes les variables globales utilisées par plusieurs stas. Le serveur doit utiliser CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream pour marshaler des pointeurs d’interface, éventuellement à partir d’autres serveurs, entre des stas. Le serveur peut éventuellement implémenter IMessageFilter dans chaque STA pour contrôler les aspects de la remise des appels par COM. Un cas dégénéré est le serveur de modèle monothread qui fonctionne com dans un sta.

  2. Serveur de modèle MTA :

    Le serveur fonctionne com dans un ou plusieurs threads, qui appartiennent tous au MTA. Les appels ne sont pas synchronisés par COM. COM crée un pool de threads dans le processus serveur, et un appel client est remis par l’un de ces threads. Les threads n’ont pas besoin de récupérer et de distribuer des messages. L’objet et la fabrique de classes doivent implémenter la synchronisation. Le serveur n’a pas besoin de marshaler les pointeurs d’interface entre les threads.

  3. Serveur de modèles mixtes :

    Pour plus d’informations, consultez la section de cet article intitulée « Modèle à threads mixtes ».

Modèles de thread dans les clients

Il existe trois catégories de clients :

  1. Client de modèle STA :

    Le client fonctionne com dans les threads associés à un ou plusieurs contrats STA. Le client doit utiliser CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream pour marshaler des pointeurs d’interface entre des stas. Un cas dégénéré est le client de modèle monothread qui fonctionne COM dans un sta. Le thread du client entre dans une boucle de message fournie par COM lorsqu’il effectue un appel sortant. Le client peut utiliser IMessageFilter pour gérer les rappels et le traitement des messages de fenêtre en attendant les appels sortants et d’autres problèmes d’accès concurrentiel.

  2. Client de modèle MTA :

    Le client fonctionne com dans un ou plusieurs threads, qui appartiennent tous au MTA. Le client n’a pas besoin de marshaler les pointeurs d’interface entre ses threads. Le client ne peut pas utiliser IMessageFilter. Les threads du client s’interrompent lorsqu’ils effectuent un appel COM à un objet hors processus et reprennent lorsque l’appel est retourné. Les appels entrants arrivent sur des threads créés et gérés par COM.

  3. Client de modèle mixte :

    Pour plus d’informations, consultez la section de cet article intitulée « Modèle à threads mixtes ».

Modèles de thread dans les serveurs in-proc (basés sur dll)

Il existe quatre catégories de serveurs in-proc, chacun pouvant être utilisé par n’importe quel client COM, quel que soit le modèle de thread utilisé par ce client. Toutefois, les serveurs in-proc doivent fournir du code de marshaling pour toute interface personnalisée (non définie par le système) qu’ils implémentent pour prendre en charge l’interopérabilité du modèle de thread, car cela nécessite généralement que COM marshale l’interface entre les appartements clients. Les quatre catégories sont les suivantes :

  1. Serveur in-proc qui prend en charge le thread unique (« main » STA) - aucune ThreadingModel valeur :

    Un objet fourni par ce serveur s’attend à être accessible par le même sta client que celui par lequel il a été créé. En outre, le serveur s’attend à ce que tous ses points d’entrée, tels que DllGetClassObject et DllCanUnloadNow, et les données globales soient accessibles par le même thread (celui associé au main STA). Les serveurs qui existaient avant l’introduction du multithreading dans COM font partie de cette catégorie. Ces serveurs ne sont pas conçus pour être accessibles par plusieurs threads. COM crée donc tous les objets fournis par le serveur dans le main STA du processus et les appels aux objets sont remis par le thread associé au main STA. D’autres appartements clients accèdent à l’objet par le biais de proxys. Les appels des autres appartements vont du proxy au stub dans le main STA (marshaling inter-thread), puis à l’objet . Ce marshaling permet à COM de synchroniser les appels à l’objet et les appels sont remis par le STA dans lequel l’objet a été créé. Le marshaling entre threads étant lent par rapport à l’appel direct, il est recommandé de réécrire ces serveurs pour prendre en charge plusieurs stas (catégorie 2).

  2. Serveur in-proc qui prend en charge le modèle d’appartement à thread unique (plusieurs stas) - marqué avec ThreadingModel=Apartment:

    Un objet fourni par ce serveur s’attend à être accessible par le même sta client que celui par lequel il a été créé. Par conséquent, il est similaire à un objet fourni par un serveur in-proc à thread unique. Toutefois, les objets fournis par ce serveur peuvent être créés dans plusieurs stas du processus. Le serveur doit donc concevoir ses points d’entrée, tels que DllGetClassObject et DllCanUnloadNow, et les données globales pour une utilisation multithread. Par exemple, si deux stas d’un processus créent simultanément deux instances de l’objet in-proc, DllGetClassObject peuvent être appelées simultanément par les deux stas. De même, DllCanUnloadNow doit être écrit afin que le serveur soit protégé contre le déchargement pendant que le code est toujours en cours d’exécution sur le serveur.

    Si le serveur fournit une seule instance de la fabrique de classes pour créer tous les objets, l’implémentation de la fabrique de classes doit également être conçue pour une utilisation multithread, car elle est accessible par plusieurs stas clients. Si le serveur crée un instance de la fabrique de classes chaque fois DllGetClassObject qu’il est appelé, la fabrique de classes n’a pas besoin d’être thread-safe. Toutefois, l’implémenteur doit synchroniser l’accès à toutes les variables globales.

    Les objets COM créés par la fabrique de classes n’ont pas besoin d’être thread-safe. Toutefois, l’accès aux variables globales doit être synchronisé par l’implémenteur. Une fois créé par un thread, l’objet est toujours accessible via ce thread et tous les appels à l’objet sont synchronisés par COM. Les appartements clients qui sont différents du STA dans lequel l’objet a été créé doivent accéder à l’objet via des proxys. Ces proxys sont créés lorsque le client marshale l’interface entre ses appartements.

    Tout client qui crée un objet STA par le biais de sa fabrique de classes obtient un pointeur direct vers l’objet . Cela est différent des objets in-proc à thread unique, où seul le main STA du client obtient un pointeur direct vers l’objet et où tous les autres stas qui créent l’objet accèdent à l’objet via un proxy. Étant donné que le marshaling entre threads est lent par rapport à l’appel direct, la vitesse peut être améliorée en modifiant un serveur in-proc à thread unique pour prendre en charge plusieurs stas.

  3. Serveur in-proc qui prend uniquement en charge MTA - marqué avec ThreadingModel=Free:

    Un objet fourni par ce serveur est sécurisé pour le MTA uniquement. Il implémente sa propre synchronisation et est accessible par plusieurs threads clients en même temps. Ce serveur peut avoir un comportement incompatible avec le modèle STA. (Par exemple, par son utilisation de la file d’attente de messages Windows d’une manière qui interrompt la pompe de messages d’un STA.) En outre, en marquant le modèle de thread de l’objet comme « Free », l’implémenteur de l’objet déclare ce qui suit : cet objet peut être appelé à partir de n’importe quel thread client, mais cet objet peut également passer des pointeurs d’interface directement (sans marshaling) à tous les threads qu’il a créés et ces threads peuvent effectuer des appels via ces pointeurs. Par conséquent, si le client passe un pointeur d’interface à un objet implémenté par le client (tel qu’un récepteur) à cet objet, il peut choisir de rappeler via ce pointeur d’interface à partir de n’importe quel thread qu’il a créé. Si le client est un STA, un appel direct à partir d’un thread, qui est différent du thread qui a créé l’objet récepteur, sera en erreur (comme illustré dans 2 ci-dessus). Par conséquent, COM garantit toujours que les clients dans les threads associés à un STA accèdent à ce type d’objet in-proc uniquement par le biais d’un proxy. En outre, ces objets ne doivent pas s’agréger avec le marshaleur à threads libres, car cela leur permet de s’exécuter directement sur les threads STA.

  4. Serveur in-proc qui prend en charge le modèle d’appartement et le thread libre - marqué avec ThreadingModel=Both:

    Un objet fourni par ce serveur implémente sa propre synchronisation et est accessible simultanément par plusieurs appartements clients. En outre, cet objet est créé et utilisé directement, au lieu d’un proxy, dans les stas ou le MTA d’un processus client. Étant donné que cet objet est utilisé directement dans les stas, le serveur doit marshaler les interfaces des objets, éventuellement à partir d’autres serveurs, entre les threads afin que son accès à n’importe quel objet d’une manière appropriée au thread soit garanti. En outre, en marquant le modèle de thread de l’objet comme « Both », l’implémenteur de l’objet indique ce qui suit : cet objet peut être appelé à partir de n’importe quel thread client, mais les rappels de cet objet au client sont effectués uniquement sur l’appartement dans lequel l’objet a reçu le pointeur d’interface vers l’objet de rappel. COM permet de créer un tel objet directement dans un STA ainsi que dans un MTA du processus client.

    Étant donné que tout appartement qui crée un tel objet obtient toujours un pointeur direct plutôt qu’un pointeur proxy, ThreadingModel "Both" les objets fournissent des améliorations de performances par rapport ThreadingModel "Free" aux objets lorsqu’ils sont chargés dans un STA.

    Étant donné qu’un ThreadingModel "Both" objet est également conçu pour l’accès MTA (il est thread-safe en interne), il peut accélérer les performances en agrégeant avec le marshaleur fourni par CoCreateFreeThreadedMarshaler. Cet objet fourni par le système est agrégé dans tous les objets appelants et les marshals personnalisés dirigent les pointeurs vers l’objet dans tous les appartements du processus. Les clients de n’importe quel appartement, qu’il s’agisse d’un STA ou d’un MTA, peuvent alors accéder directement à l’objet au lieu d’un proxy. Par exemple, un client de modèle STA crée l’objet in-proc dans STA1 et marshale l’objet en STA2. Si l’objet ne s’agrège pas avec le marshaler à thread libre, STA2 accède à l’objet via un proxy. Si c’est le cas, le marshaleur à thread libre fournit STA2 avec un pointeur direct vers l’objet

    Remarque

    Lors de l’agrégation avec le marshaleur à thread libre, vous devez faire attention. Par exemple, supposons qu’un objet marqué comme ThreadingModel "Both" (et agrégeant également avec le marshaleur à threads libres) a un membre de données qui est un pointeur d’interface vers un autre objet dont ThreadingModel la valeur est « Apartment ». Supposons ensuite qu’un STA crée le premier objet et que lors de la création, le premier objet crée le deuxième objet. Conformément aux règles décrites ci-dessus, le premier objet contient désormais un pointeur direct vers le deuxième objet. Supposons maintenant que le STA marshale le pointeur d’interface vers le premier objet vers un autre appartement. Étant donné que le premier objet s’agrège avec le marshaleur à thread libre, un pointeur direct vers le premier objet est attribué au deuxième appartement. Si le deuxième appartement appelle ensuite via ce pointeur, et si cet appel provoque l’appel du premier objet via le pointeur d’interface vers le deuxième objet, une erreur s’est produite, car le deuxième objet n’est pas destiné à être appelé directement à partir du deuxième appartement. Si le premier objet contient un pointeur vers un proxy vers le deuxième objet plutôt qu’un pointeur direct, une erreur différente est générée. Les proxys système sont également des objets COM associés à un seul appartement. Ils gardent la trace de leur appartement afin d’éviter certaines circularités. Par conséquent, un objet appelant sur un proxy associé à un autre appartement que le thread sur lequel l’objet s’exécute reçoit le RPC_E_WRONG_THREAD retour du proxy et l’appel échoue.

Interopérabilité du modèle de thread entre les clients et les objets in-process

Toutes les combinaisons d’interopérabilité de modèle de thread sont autorisées entre les clients et les objets in-process.

COM permet à tous les clients de modèle STA d’interagir avec les objets in-proc à thread unique en créant et en accédant à l’objet dans le main STA du client et en le marshalant vers le sta client qui a appelé CoCreateInstance[Ex].

Si un MTA dans un client crée un serveur in-proc de modèle STA, COM lance un STA « hôte » dans le client. Cet hôte STA crée l’objet et le pointeur d’interface est marshalé vers le MTA. De même, lorsqu’un STA crée un serveur in-proc MTA, COM lance un MTA hôte dans lequel l’objet est créé et marshalé vers le STA. L’interopérabilité entre le modèle monothread et le modèle MTA est gérée de la même façon, car le modèle monothread n’est qu’un cas dégénéré du modèle STA.

Le code de marshaling doit être fourni pour toute interface personnalisée qu’un serveur in-proc implémente s’il souhaite prendre en charge l’interopérabilité qui nécessite QUE COM marshale l’interface entre les appartements clients. Pour plus d’informations, consultez la section RÉFÉRENCEs ci-dessous.

Relation entre le modèle threading et l’objet de fabrique de classes retourné

Une définition précise des serveurs in-proc en cours de « chargement dans » des appartements est expliquée dans les deux étapes suivantes :

  1. Si la DLL qui contient la classe de serveur in-proc n’a pas été précédemment chargée (mappée dans l’espace d’adressage du processus) par le chargeur du système d’exploitation, cette opération est effectuée et COM obtient l’adresse de la DLLGetClassObject fonction exportée par la DLL. Si la DLL a déjà été chargée par un thread associé à un appartement, cette étape est ignorée.

  2. COM utilise le thread (ou, dans le cas du MTA, l’un des threads) associé à l’appartement de « chargement » pour appeler la DllGetClassObject fonction exportée par la DLL en demandant le CLSID de la classe nécessaire. L’objet de fabrique retourné est ensuite utilisé pour créer des instances d’objets de la classe .

    La deuxième étape (l’appel de DllGetClassObject par COM) se produit chaque fois qu’un client appelle CoGetClassObject/CoCreateIntance[Ex], même à partir du même appartement. En d’autres termes, DllGetClassObject peut être appelé plusieurs fois par un thread associé au même appartement ; tout dépend du nombre de clients dans cet appartement qui tentent d’accéder à un objet de fabrique de classe pour cette classe.

Les auteurs d’implémentations de classe et, en fin de compte, l’auteur du package DLL d’un ensemble donné de classes ont la liberté totale de décider quel objet de fabrique retourner en réponse à l’appel de fonction DllGetClassObject . L’auteur du package DLL a le dernier mot, car le code « derrière » le point d’entrée unique à l’échelle DllGetClassObject() de la DLL est ce qui a le premier et potentiellement le dernier droit de décider ce qu’il faut faire. Les trois possibilités typiques sont les suivantes :

  1. DllGetClassObject retourne un objet de fabrique de classe unique par thread appelant (ce qui signifie un objet de fabrique de classe par STA et potentiellement plusieurs fabriques de classes dans le MTA).

  2. DllGetClassObject retourne toujours le même objet de fabrique de classe, quel que soit l’identité du thread appelant ou le type d’appartement associé au thread appelant.

  3. DllGetClassObject retourne un objet de fabrique de classe unique par appartement appelant (un par appartement dans STA et MTA).

Il existe d’autres possibilités pour la relation entre les appels à DllGetClassObject et l’objet de fabrique de classe retourné (par exemple, un nouvel objet de fabrique de classe par appel à DllGetClassObject), mais elles ne semblent pas actuellement être utiles.

Résumé des modèles de thread d’objet et de client pour les serveurs In-Proc

Le tableau suivant récapitule l’interaction entre les différents modèles de thread lorsqu’un thread client appelle CoGetClassObject pour la première fois sur une classe implémentée en tant que serveur in-proc.

Types de clients/threads :

  • le client s’exécute dans un thread associé au « main » STA (premier thread à appeler CoInitialize ou CoInitializeEx avec COINIT_APARTMENTTHREADED indicateur) : appelez ce STA0 (également appelé modèle monothread).
  • le client s’exécute dans un thread associé à dans tout autre STA [ASCII 150] appeler ce STA*.
  • le client s’exécute dans un thread associé à dans le MTA.

Types de serveurs DLL :

  • Le serveur n’a pas ThreadingModel de clé : appelez ce « Aucun ».
  • Le serveur est marqué « Appartement » : appelez-le « Apt ».
  • Le serveur est marqué « Gratuit ».
  • Le serveur est marqué « Les deux ».

Lorsque vous lisez le tableau ci-dessous, gardez à l’esprit la définition ci-dessus de « chargement » d’un serveur dans un appartement.

Client         Server                 Result
STA0           None                   Direct access; server loaded into STA0  
STA*           None                   Proxy access; server loaded into STA0.  
MTA            None                   Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;  
STA0           Apt                    Direct access; server loaded into STA0  
STA*           Apt                    Direct access; server loaded into STA*  
MTA            Apt                    Proxy access; server loaded into an STA created automatically by COM.
STA0           Free                   Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA*           Free                   Same as STA0->Free
MTA            Free                   Direct access
STA0           Both                   Direct access; server loaded into STA0
STA*           Both                   Direct access; server loaded into STA*
MTA            Both                   Direct access; server loaded into the MTA

References

Documentation du Kit de développement logiciel (SDK) sur l’interface CoRegisterMessageFilter() et .IMessageFilter