Descriptions et fonctionnement des modèles de thread OLE
Cet article décrit les modèles de thread OLE.
Version du produit d’origine : modèles de thread OLE
Numéro de base de connaissances d’origine : 150777
Résumé
Les objets COM peuvent être utilisés dans plusieurs threads d’un processus. Les termes « Single-threaded Apartment » (STA) et « Multi-threaded Apartment » (MTA) sont utilisés pour créer un framework conceptuel permettant de 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 les threads. Les composants et leurs clients choisissent entre les deux modèles d’appartement suivants actuellement pris en charge par COM :
Modèle d’appartement à thread unique (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 à thread unique, où un seul thread d’un processus donné utilise COM, est appelé modèle de thread unique. Les précédents ont parfois fait référence au modèle STA simplement comme « modèle d’appartement ».
Modèle D’appartement multithread (MTA) : un ou plusieurs threads utilisent COM et les appels aux objets COM associés à l’assistant MTA sont effectués directement par tous les threads associés à l’assistant MTA sans 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 comme « modèle à thread libre ».
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 « mixed-model ».
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 NT 4.0 et Windows 95 avec DCOM95.
Aperçu
Les modèles de thread dans COM fournissent le mécanisme des composants qui utilisent différentes architectures de thread pour travailler 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 thread unique et ne pas synchroniser les appels simultanés à partir de clients. Si un tel objet est appelé simultanément par plusieurs threads, il plante ou provoque des erreurs. COM fournit les mécanismes permettant de traiter cette interopérabilité des architectures de thread.
Même les composants prenant en compte les threads ont souvent besoin de services de synchronisation. Par exemple, les composants qui ont une interface graphique utilisateur (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 » a plusieurs aspects connexes. Tout d’abord, il s’agit d’une construction logique pour réfléchir à la concurrence, comme la façon dont les threads se rapportent à 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 par rapport aux 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 » qui est subdivisé en un ensemble de « locaux » connexes mais différents « locaux » appelés « appartements ». Un appartement est un « conteneur logique » qui crée une association entre les objets et, dans certains cas, des threads. Les threads ne sont pas des appartements, même s’il peut y avoir un seul thread logiquement associé à un appartement dans le modèle STA. Les objets ne sont pas des appartements, bien que chaque objet soit associé à un seul et à un seul appartement. Mais les appartements sont plus qu’une 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.
En savoir plus
Un appartement à thread unique (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 au système COM (généralement en marshaling) sur le thread. AN 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 ou un processus différent), son pointeur d’interface doit être marshalé vers cet appartement où un nouveau proxy est créé. Si les règles du modèle d’appartement sont suivies, aucun appel direct d’autres threads du 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 en cours d’exécution dans une sta ne fonctionne pas correctement si elle est exécutée sur des threads supplémentaires.
Le thread associé à un sta doit appeler CoInitialize
ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
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 à des objets dans un sta à l’aide de messages de fenêtre, comme décrit plus loin dans cet article.
Le « sta principal » est le thread qui appelle ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
commence CoInitialize
par un processus donné. Le sta principal d’un processus doit rester actif jusqu’à ce que tout le travail COM soit terminé, car certains objets in-proc sont toujours chargés dans le sta principal, 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 tout thread puisse appeler une implémentation d’objet directement sans l’interposition du code système. Les pointeurs d’interface vers n’importe quel objet de l’instance MTA peuvent être passés entre les threads associés à l’assistant MTA sans avoir à être marshalés. Tous les threads du processus qui appellent CoInitializeEx(NULL, COINIT_MULTITHREADED)
sont associés à l’assistant MTA. Contrairement à la sta décrite ci-dessus, les threads d’une MTA n’ont pas besoin de récupérer et de distribuer des messages de fenêtre pour les objets associés pour recevoir des appels entrants. COM ne synchronise pas les appels aux objets dans un MTA. Les objets d’une MTA doivent protéger leur état interne contre l’altération par l’interaction de plusieurs threads simultanés et ils ne peuvent pas faire d’hypothèses sur le contenu du stockage local thread-local restant constant entre les appels de méthodes différents.
Un processus peut avoir n’importe quel nombre de stas, mais, au plus, peut avoir un MTA. L’assistant MTA se compose d’un ou plusieurs threads. Les stas ont chacun un thread. Un thread appartient, au plus, à un appartement. Les objets appartiennent à un seul et à 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). Voir les informations ci-dessous sur CoCreateFreeThreadedMarshaler
.
Un processus choisit l’un des modèles de threading fournis par COM. Un processus de modèle STA a un ou plusieurs STA 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 une MTA et un nombre quelconque d’AST.
Modèle d’appartement monothread
Le thread d’un STA doit appeler CoInitialize
ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
récupérer et distribuer des messages de fenêtre, car COM utilise des messages de fenêtre pour synchroniser et distribuer la remise des appels à un objet dans ce modèle. Pour plus d’informations, consultez la section RÉFÉRENCEs ci-dessous.
Serveur prenant en charge le modèle STA :
Dans le modèle STA, les appels à un objet sont synchronisés par COM de la même façon 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
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 vers le 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, la reçoit. La procédure de fenêtre est utilisée par le runtime COM pour « raccorder » le thread associé à la fonction STA, car le runtime COM se trouve sur les deux côtés de l’appel du thread appartenant à COM au thread de STA. Le runtime COM (maintenant exécuté 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é par l’appel de méthode inverse l’appel « up » ; l’appel retourne au stub et au runtime COM, qui passe le contrôle au thread d’exécution COM via un message de fenêtre, qui retourne ensuite via le canal COM à l’appelant d’origine.
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 transfert du mécanisme 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é à la sta de l’objet, les implémentations d’interface de l’objet n’ont pas besoin de fournir la synchronisation.
Note
L’objet peut être recréé 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. Une méthode courante dans laquelle cela se produit est si un objet STA effectue un appel sortant (inter-appartement/cross-process) à l’aide de COM. Cela est identique à la façon dont une procédure de fenêtre peut être re-entrée si elle récupère et distribue des messages lors du traitement d’un message. COM n’empêche pas une nouvelle entrée sur le même thread, mais empêche l’exécution simultanée. Il fournit également un moyen par lequel la réentrance liée à COM peut être gérée. Pour plus d’informations, consultez la section RÉFÉRENCEs ci-dessous. L’objet n’est pas entré à nouveau si les implémentations de méthode n’appellent pas son appartement ou récupèrent et distribuent des messages.
Responsabilités des clients 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 des interfaces d’un objet entre appartements à l’aide CoMarshalInterThreadInterfaceInStream
et CoGetInterfaceAndReleaseStream
. Par exemple, si Apartment 1 dans le client a un pointeur d’interface, et Apartment 2 nécessite son utilisation, Apartment 1 doit marshaler l’interface à l’aide CoMarshalInterThreadInterfaceInStream
de . 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. L’appartement 2 doit passer cette interface de flux pour CoGetInterfaceAndReleaseStream
annuler l’exécution de l’interface sur l’objet sous-jacent et récupérer un pointeur vers un proxy via lequel il peut accéder à l’objet.
L’appartement principal d’un processus donné doit rester actif jusqu’à ce que le client ait terminé tout le travail COM, car certains objets in-proc sont chargés dans l’appartement principal. (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)
.
Note
Les implémentations actuelles de COM permettent à un thread qui n’initialise pas explicitement COM de faire partie de l’instance MTA. Un thread qui n’initialise pas COM fait partie de l’assistant MTA uniquement s’il commence à utiliser COM après au moins un autre thread du processus a déjà appelé CoInitializeEx(NULL, COINIT_MULTITHREADED)
. (Il est même possible que COM lui-même ait initialisé le MTA lorsqu’aucun thread client n’a été explicitement effectué ; 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 doit effectuer un travail COM initialise COM en appelantCoInitializeEx
, puis, à l’achèvement du travail COM, appel .CoUninitialize
Le coût de l’initialisation inutile d’un MTA est minimal.
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 prenant 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 à l’assistant MTA.
Responsabilités des clients 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 direct. 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 à l’assistant MTA tandis que tous les threads créés par l’application associés à l’assistant MTA sont bloqués lors des appels sortants. Dans ce cas et en général, les appels entrants sont remis sur les 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 de thread mixte
Un processus qui prend en charge le modèle de thread mixte utilise un MTA et un ou plusieurs stas. Les pointeurs d’interface doivent être marshalés entre tous les appartements, mais peuvent être utilisés sans marshaling dans l’assistant MTA. Les appels aux objets d’une sta sont synchronisés par COM pour s’exécuter sur un seul thread, tandis que les appels aux objets dans l’assistant MTA ne le sont pas. 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.
Note
Consultez la documentation du KIT de développement logiciel (SDK) et CoCreateFreeThreadedMarshaler()
la discussion de cette API ci-dessous 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é à l’assistant MTA et vice versa, à partir de plusieurs appartements.
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 utilisateur utilisateur. La prise en charge du modèle STA est plus facile, car COM fournit une 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 meilleure, car la synchronisation est utilisée pour les sections plus petites du code, plutôt que pour l’appel d’interface entier 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 planifient l’exécution 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, ils s’exécutent moins efficacement, car ils utilisent des primitives de synchronisation de thread inutiles.
Marquage du modèle de 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 inclus. ExEs, 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 ont été appelées au moment où le serveur DLL est chargé. 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 elle. 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 à thread unique.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.
Note
ThreadingModel
est une valeur nommée, et non une sous-clé de InprocServer32, comme indiqué incorrectement dans certaines versions antérieures de la documentation Win32.
Les modèles de thread des serveurs in-proc sont abordés 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 threading est par CLSID, et non par package de code/DLL. Toutefois, les points d’entrée d’API nécessaires à « bootstrap » et interrogent tous les serveurs in-proc (DLLGetClassObject()
, DLLCanUnloadNow()
) doivent être thread-safe pour n’importe quel serveur in-proc qui prend en charge plusieurs threads (ce qui signifie 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é « substitution » de COM (par exemple, le substitut fourni par le système DLLHOST.EXE) 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 threading
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 se trouvent dans différents processus et COM est impliqué dans le passage d’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 des 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é du modèle de threading 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 threading est plus complexe. 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é à la sta qui « contient » l’objet. Malgré la complexité ajoutée, toutes les combinaisons d’interopérabilité du modèle de threading 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 :
Serveur de modèle STA :
Le serveur effectue le travail COM dans un ou plusieurs stas. Les appels entrants sont synchronisés par COM et remis par le thread associé à la sta dans laquelle l’objet a été créé. Les appels de méthode de fabrique de classes 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
etCoGetInterfaceAndReleaseStream
marshaler des pointeurs d’interface, éventuellement à partir d’autres serveurs, entre des stas. Le serveur peut éventuellement implémenterIMessageFilter
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 à thread unique qui effectue le travail COM dans une sta.Serveur de modèles MTA :
Le serveur effectue le travail COM dans un ou plusieurs threads, qui appartiennent tous à l’assistant 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.
Serveur de modèles mixtes :
Pour plus d’informations, consultez la section de cet article intitulée « Modèle de thread mixte ».
Modèles de thread dans les clients
Il existe trois catégories de clients :
Client de modèle STA :
Le client effectue un travail COM dans des threads associés à un ou plusieurs stas. Le client doit utiliser
CoMarshalInterThreadInterfaceInStream
etCoGetInterfaceAndReleaseStream
marshaler des pointeurs d’interface entre des stas. Un cas dégénéré est le client de modèle à thread unique qui effectue le travail COM dans une sta. Le thread du client entre une boucle de message fournie PAR COM lorsqu’il effectue un appel sortant. Le client peut utiliserIMessageFilter
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.Client de modèle MTA :
Le client effectue un travail COM dans un ou plusieurs threads, qui appartiennent tous à l’assistant 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 reprendnt lorsque l’appel est retourné. Les appels entrants arrivent sur des threads créés et managés COM.Client de modèle mixte :
Pour plus d’informations, consultez la section de cet article intitulée « Modèle de thread mixte ».
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 s’ils prennent 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 :
In-proc Server qui prend en charge le threading unique (« main » STA) - aucune
ThreadingModel
valeur :Un objet fourni par ce serveur s’attend à être accessible par le même client STA par lequel il a été créé. En outre, le serveur s’attend à ce que tous ses points d’entrée, tels que
DllGetClassObject
etDllCanUnloadNow
, et les données globales soient accessibles par le même thread (celui associé au sta principal). Les serveurs qui existaient avant l’introduction de multithreading dans COM se trouvent dans cette catégorie. Ces serveurs ne sont pas conçus pour être accessibles par plusieurs threads, de sorte que COM crée tous les objets fournis par le serveur dans la sta principale du processus et les appels aux objets sont remis par le thread associé à la sta principale. D’autres appartements clients accèdent à l’objet par le biais de proxys. Les appels des autres appartements passent du proxy au stub dans le sta principal (marshaling entre threads) 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 est lent par rapport à l’appel direct. Il est donc recommandé que ces serveurs soient réécrites pour prendre en charge plusieurs stas (catégorie 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 client STA 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, de sorte que le serveur doit concevoir ses points d’entrée, tels que
DllGetClassObject
etDllCanUnloadNow
, et les données globales pour une utilisation multithread. Par exemple, si deux stas d’un processus créent deux instances de l’objet in-proc simultanément,DllGetClassObject
peuvent être appelées simultanément par les deux stas. De même,DllCanUnloadNow
il 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 une 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 de la STA dans laquelle 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 via sa fabrique de classes obtient un pointeur direct vers l’objet. Cela est différent des objets in-proc à thread unique, où seul le sta principal du client obtient un pointeur direct vers l’objet et 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.
Serveur in-proc qui prend uniquement en charge MTA - marqué avec
ThreadingModel=Free
:Un objet fourni par ce serveur est sécurisé uniquement pour l’assistant MTA. 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, en utilisant la file d’attente de messages Windows d’une manière qui interrompt la pompe de message d’un STA.) En outre, en marquant le modèle de thread de l’objet comme « Libre », l’implémenteur de l’objet indique ce qui suit : cet objet peut être appelé à partir de n’importe quel thread client, mais cet objet peut également transmettre directement des pointeurs d’interface (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 transmet 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, différent du thread qui a créé l’objet récepteur est en erreur (comme illustré dans 2 ci-dessus). Par conséquent, COM garantit toujours que les clients dans les threads associés à un STA obtiennent l’accès à ce type d’objet in-proc uniquement via 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 des threads STA.
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 des stas ou l’assistant MTA d’un processus client. Étant donné que cet objet est utilisé directement dans les stas, le serveur doit marshaler des interfaces d’objets, éventuellement à partir d’autres serveurs, entre threads afin que son accès à n’importe quel objet d’une manière appropriée au threading 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 tous les rappels de cet objet vers le client seront effectués uniquement sur l’appartement dans lequel l’objet a reçu le pointeur d’interface vers l’objet de rappel. COM permet à un tel objet d’être créé directement dans une sta ainsi que dans une 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 des performances surThreadingModel "Free"
les 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 parCoCreateFreeThreadedMarshaler
. Cet objet fourni par le système est agrégé à tous les objets appelants et marshale des pointeurs directs vers l’objet dans tous les appartements du processus. Les clients d’un appartement, qu’il s’agisse d’un sta ou DTA, peuvent ensuite 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 sur STA2. Si l’objet n’est pas agrégé avec le marshaleur à threads libres, STA2 obtient l’accès à l’objet via un proxy. Si c’est le cas, le marshaleur à threads libres fournit STA2 avec un pointeur direct vers l’objetNote
Vous devez prendre soin de l’agrégation avec le marshaleur à threads libres. Par exemple, supposons qu’un objet marqué comme
ThreadingModel "Both"
(et également agrégé avec le marshaleur à thread libre) possède un membre de données qui est un pointeur d’interface vers un autre objet dontThreadingModel
l’objet est « Apartment ». Supposons ensuite qu’un STA crée le premier objet et 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 maintenant 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 agrège avec le marshaleur à thread libre, un pointeur direct vers le premier objet est donné au deuxième appartement. Si le deuxième appartement appelle ensuite ce pointeur et si cet appel entraîne 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 directement appelé à 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, cela entraîne une erreur différente. Les proxys système sont également des objets COM associés à un seul appartement. Ils gardent le suivi de leur appartement afin d’éviter certaines circulaires. Ainsi, un objet appelant sur un proxy associé à un autre appartement que le thread sur lequel l’objet est en cours d’exécution recevra le retour RPC_E_WRONG_THREAD du proxy et l’appel échouera.
Interopérabilité du modèle de thread entre les clients et les objets in-process
Toutes les combinaisons d’interopérabilité du modèle de threading sont autorisées entre les clients et les objets in-process.
COM permet à tous les clients de modèle STA d’interagir avec des objets in-threading in-proc en créant et en accédant à l’objet dans la sta principale du client et en le marshalant sur le client STA appelé CoCreateInstance[Ex]
.
Si un MTA dans un client crée un modèle STA dans un serveur in-proc, COM fait tourner un sta « hôte » dans le client. Cet hôte STA crée l’objet et le pointeur d’interface est marshalé vers l’assistant MTA. De même, lorsqu’un STA crée un serveur MTA in-proc, COM fait tourner un MTA hôte dans lequel l’objet est créé et marshalé vers la sta. L’interopérabilité entre le modèle monothreading et le modèle MTA est gérée de la même façon, car le modèle à thread unique 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 exige 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 de threading et l’objet fabrique de classes retourné
Une définition précise des serveurs in-proc en cours d’appartements est expliquée dans les deux étapes suivantes :
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 été précédemment chargée par un thread associé à un appartement, cette étape est ignorée.COM utilise le thread (ou, dans le cas du MTA, l’un des threads) associé à l’appartement « chargement » pour appeler la
DllGetClassObject
fonction exportée par la DLL demandant le CLSID de la classe nécessaire. L’objet factory retourné est ensuite utilisé pour créer des instances d’objets de la classe.La deuxième étape (l’appel de
DllGetClassObject
com) se produit chaque fois qu’un client appelleCoGetClassObject
/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 de cet appartement qui essaient d’accéder à un objet fabrique de classe pour cette classe.
Les auteurs d’implémentations de classes et, en fin de compte, l’auteur du package DLL d’un ensemble donné de classes disposent d’une liberté totale pour décider de l’objet de fabrique à retourner en réponse à l’appel DllGetClassObject
de fonction. L’auteur du package DLL a le mot de passe ultime, car le code « behind » le point d’entrée à l’échelle DllGetClassObject()
de la DLL unique est ce qui a le premier et potentiellement le droit final de décider de ce qu’il faut faire. Les trois possibilités typiques sont les suivantes :
DllGetClassObject
retourne un objet de fabrique de classes unique par thread appelant (ce qui signifie qu’un objet fabrique de classe par STA et potentiellement plusieurs fabriques de classes dans le MTA).DllGetClassObject
retourne toujours le même objet de fabrique de classes, quelle que soit l’identité du thread appelant ou le type d’appartement associé au thread appelant.DllGetClassObject
retourne un objet d’usine 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 ils ne semblent pas actuellement être utiles.
Résumé des modèles de thread d’objets et clients pour les serveurs in-Proc
Le tableau suivant récapitule l’interaction entre différents modèles de thread lorsqu’un thread client appelle CoGetClassObject
d’abord une classe implémentée en tant que serveur in-proc.
Types de clients/threads :
- le client est en cours d’exécution dans un thread associé à la sta « main » (premier thread à appeler
CoInitialize
ouCoInitializeEx
avecCOINIT_APARTMENTTHREADED
l’indicateur)-appeler ce STA0 (également appelé modèle de thread unique). - le client s’exécute dans un thread associé à n’importe quel autre STA [ASCII 150] appelle ce STA*.
- le client s’exécute dans un thread associé à l’assistant MTA.
Types de serveurs DLL :
- Le serveur n’a pas
ThreadingModel
d’appel de clé : « Aucun ». - Le serveur est marqué « Appartement » --appelez ce « 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 faite 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 et IMessageFilter
l’interfaceCoRegisterMessageFilter()
.