Inside COM+ Base Services, Microsoft Press.
Traduction personnelle. But pédagogique.


Les Types d'Appartements

Windows a évolué du mode 16 bits au mode 32 bits, les librairies C et MFC ont évoluées pour prendre en compte la gestion des threads. Ce fut douloureux mais l'avantage est immense.

A partir du moment ou le support du multithreading a été ajouté à COM, de nombreuses décisions de conception ont été affectées par la quantité de code non protégé (thread-unsafe). La première version de COM avec une gestion des threads fut livrée avec Microsoft Windows NT 3.51. Microsoft a voulu que les anciens composants puissent coexister avec les nouveaux composants multithreads. Pour ce faire, Microsoft a défini des modèles de threading. L'unité de base dans COM+ en est l'appartement. Un appartement est un ensemble de règles sur les threads partagé par un groupe d'objets. Un appartement ne peut pas être comparé avec un objet Win32 comme un thread ou un processus. Le concept de l'appartement sert à clarifier les règles sur le threading appliquées à un objet COM+. Ces règles diffèrent pour chaque type d'appartement.

Les trois types d'appartements sont les appartements simple thread (STAs=single-threaded apartments), les appartements neutres (NAs=neutral apartments), et les appartements multithread (MTAs=multithreaded apartments). Les STAs ne peuvent avoir qu'un seul thread par appartement, tandis que les NAs et MTAs peuvent avoir plusieurs threads par appartement. Les appels de méthode aux objets dans un STA sont automatiquement synchronisés et dispatchés en utilisant les queues de messages Windows, tandis que les appels de méthodes aux objets dans un MTA ou NA ne le sont pas. (La plate-forme Windows CE ne supporte que le modèle MTA.)

Le modèle STA notifie un objet d'un appel de méthode en postant un message dans une queue de messages d'une fenêtre. Chaque composant qui supporte le modèle STA doit contenir une boucle de message ou rien ne se produira::

MSG msg;
while(GetMessage(&msg, 0, 0, 0))
    DispatchMessage(&msg);

En se servant des queues de messages pour délivrer les appels de méthodes, COM+ a des hooks sur chaque objet dans un STA. Les messages sont traités séquentiellement et il n'y a pas besoin de synchroniser les données partagées. Le modèle MTA n'utilise pas les queues de messages.

De part le passé, avec les combinaisons de STAs et de MTAs dans un seul processus, on parlait de single-threading, de apartment-threading, de free-threading, et des mixed-threading. Ces terminologies ne sont plus utilisées. Les trois modèles de types d'appartement peuvent être combinés dans un même processus; un processus peut contenir zéro ou plusieurs STAs mais au plus un MTA et un NA.

Les modèles de threading de COM+ permettent aux clients et aux composants d'utiliser différentes architectures de threading. De la perspective du client, tous les objets semblent utiliser le modèle de threading du client. Du point de vue d'un objet, tous les clients semblent supporter le modèle de threading de l'objet. Les clients et les composants ont différentes responsabilités en relation dans les modèles de threading. Les clients peuvent utiliser la fonction Win32 CreateThread pour lancer de nouveaux threads et accéder à des objets COM+ dans ces threads. Cette technique est utilisée pour améliorer la vitesse d'une application ou pour permettre à une application d'accéder à plusieurs objets en même temps. Cependant, les composants appellent rarement la fonction CreateThread et sont fortement encouragés à ne pas le faire. A la place; un composant déclare simplement un modèle de threading et laisse COM+ gérer les concurrences d'accès en terme de création de threads.

Les Appartements STA (Single-Threaded)

Le modèle STA descend de l'ancien modèle utilisé par OLE. Les objets ne peuvent être accédés que par un seul thread dans un processus. Le modèle STA moderne dépasse cette limitation; il permet à un seul processus d'avoir plusieurs STAs. Chaque STA a un seul thread, et le fait d'avoir plusieurs STAs dans un même processus fait que plusieurs threads peuvent utiliser des objets COM+ différents en même temps. Les anciens composants tourne dans une seule STA, le STA principal.

Un thread particulier déclarer son modèle de threading en appelant la fonction CoInitializeEx. Le second paramètre de CoInitializeEx spécifie si le thread supporte le modèle STA (COINIT_APARTMENTTHREADED) ou le modèle MTA (COINIT_MULTITHREADED). La fonction obsolète, CoInitialize, appelle la fonction CoInitializeEx avec le paramètre COINIT_ APARTMENTTHREADED. Les deux appels suivants sont équivalents, car les anciens composants supportent le modèle STA:

// Single-threaded apartment (STA)
// This code is equivalent to CoInitialize(NULL).
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

Le modèle STA tous les objets COM+ instanciés par son thread, donc tous les appels de méthodes sur un objet sont exécutés par le thread qui a créé l'objet. L'objet a une affinité avec le thread et c'est important. Par exemple, certains objets utilisent les TLS pour associer des données à un thread. Un objet qui utilise le TLS a besoin de son thread pour exécuter toutes ces méthodes. Si un thread différent exécute une méthode, l'accès aux données TLS échoue. Les objets qui utilisent le modèle MTA ou NA ne peuvent pas utiliser le TLS car ils peuvent être accédés par différents threads. Seuls les objets avec un modèle STA ont une affinité de thread.

Les appels de méthodes sur les objets dans un STA se font par les queues de messages d'une fenêtre. Ce ne veut pas dire qu'un composant doit afficher une interface utilisateur ou créer un fenêtre. Le premier appel à la fonction CoInitializeEx avec le paramètre COINIT_APARTMENTTHREADED dans un processus appelle la fonction Win32 RegisterClass afin d'enregistrer la classe de fenêtre OleMainThreadWndClass. Pendant cela et pour tout nouvel appel à la fonction CoInitializeEx avec le paramètre COINIT_APARTMENTTHREADED, le système appelle automatiquement la fonction CreateWindowEx pour créer un fenêtre cachée avec le type de classe OleMainThreadWndClass pour chaque STA. La queue de message de cette fenêtre cachée est utilisée pour synchroniser et dispatcher les appels de méthodes COM+ sur cette objet. Pour cette raison, le thread associé avec le STA doit récupérer et dispatcher les événements des messages Windows en utilisant les fonctions GetMessage et DispatchMessage.

Vous pouvez utiliser Microsoft Spy++, fourni avec Microsoft Visual C++, pour voir la fenêtre cachée; voir Figure 4-1.

Figure 4-1. The fenêtre cachée STA avec Microsoft Spy++.

Cette architecture à base de queue de messages résout les problèmes d'accès pour les appels concurrents à un objet dans un STA. Les appels sont sérializés automatiquement. L'objet reçoit les appels de méthodes dans l'ordre de récupération des messages dans la queue. Notez que les appels de méthodes sont toujours faits sur le thread qui a instancié l'objet. Vous pouvez vérifier cela en utilisant les fonctions Win32 GetCurrentThreadId ou CoGetCurrentProcess pour obtenir l'identifiant de thread (ThreadID) dans chaque méthode d'un objet. L'identifiant de thread est toujours le même pour tous les objets dans un STA.

Dans certaines circonstances, un objet peut être réentré par le même thread, de la même manière qu'une procédure de fenêtre peut être réentrante. Si une méthode de l'objet traite les messages Windows, un autre appel à une méthode peut être dispatché. Vous pouvez éviter ce problème en ne traitant pas les messages Windows durant un appel de méthode. Un objet peut aussi être réentrant si une méthode fait un appel sortant et que la méthode sortante rappelle l'objet, comme avec le mécanisme des points de connexion. COM+ ne vous protège pas de ces appels réentrant; il garanti seulement que les appels ne s'exécute pas simultanément.

Un mécanisme d'invocation de méthodes basé sur les messages dans un STA est utilisé même si le client et le composant ne sont pas sur la même machine. Voir Figure 4-2. Chaque nombre indique un thread séparé. Dans le premier thread, le client appelle le proxy, qui appel le channel. (Le channel COM+ est un wrapper autour de l'infrastructure de marshalling RPC.) Alors un second thread est créé pour bloquer pendant l'envoi des données sur le réseau via un RPC. Pendant ce temps, le premier thread attend dans sa boucle de message Windows pour traiter d'autres messages. Cette boucle de message est exécutée jusqu'à que l'appel RPC fait par le second thread le notifie qu'une réponse a été reçue.

Figure 4-2. Le fonctionnement interne du modèle STA.

Sur le réseau, un thread côté serveur reçoit le paquet de données et poste un message contenant la requête client à la queue de message de la fenêtre cachée. Une autre boucle de message est nécessaire pour prendre les messages de la queue et les dispatcher à la procédure de fenêtre interne à la channel, qui à son tour appelle le stub en utilisant le thread de l'objet. Ensuite, le stub désassemble les paramètres et appelle la méthode demandée de l'objet. Le processus de déssassemblage des paramètres pour la transmission est appelé marshaling et est fait aussi de manière inverse une fois que la méthode a fini de s'exécuter. (Pour plus d'informations sur le marshaling, voir les interfaces IRpcProxyBuffer, IRpcChannelBuffer, et IRpcStubBuffer.)

Notez que quatre threads—deux côté client et deux côté composant—sont requit pour que le modèle STA fonctionne. Une optimisation interne du modèle STA est possible si le client et les composant s'exécutent sur la même machine. Dans ce cas, COM+ n'utilise que deux threads—un pour le client et un pour le composant. Dans ce cas, les besoins en réentrance du modèle STA sont assurés par une fonction callback système sur la couche transport RPC qui récupère et dispatch les messages de fenêtre.

Les Filtres de Message

Un composant exécutable peut implémenter l'interface IMessageFilter dans chaque STA pour contrôler les aspects du mécanisme d'appel employé par COM+. La construction d'un filtre de message permet à une application qui utilise les STAs de mieux gérer les appels COM+ avec les threads à interfaces graphique. Vous pouvez utiliser cette technique pour annuler un appel de méthode trop long si l'utilisateur le désire. Notez que les filtres de message ne fonctionnement qu'avec le modèle STA model, et qu'ils ne fournissent pas un mécanisme générique d'abandon d'appel de méthode.

Une application qui propose un filtre de message doit fournir à COM+ un pointeur vers son implémentation de l'interface IMessageFilter via la fonction CoRegisterMessageFilter:

IMessageFilter* pMF = new CMessageFilter;
IMessageFilter* pOldMF;

CoRegisterMessageFilter(pMF, &pOldMF);

CoRegisterMessageFilter installe le nouveau filtre de message and retourne un pointeur vers l'ancien filtre, permettant ainsi de le rétablir plus tard. (Bien que COM+ fournit un filtre par défaut pour les STAs, les pointeur retourné sera NULL.) La définition IDL de l'interface IMessageFilter est décrite ci-dessous.

interface IMessageFilter : IUnknown
{
    // Called in a component to notify it of an incoming 
    // method call
    DWORD HandleInComingCall(
        [in] DWORD dwCallType,  // Type of incoming call
        [in] HTASK htaskCaller, // HTASK of the calling task
        [in] DWORD dwTickCount, // Elapsed time since call 
                                //   was made
        [in] LPINTERFACEINFO lpInterfaceInfo); // More info 
                                               // (see below)

    // Called in a client to notify it that the component has 
    // rejected or postponed a call
    DWORD RetryRejectedCall(
        [in] HTASK htaskCallee,   // Server task handle
        [in] DWORD dwTickCount,   // Elapsed tick count
        [in] DWORD dwRejectType); // Returned rejected message

    // Called in a client when a window message is received 
    // while a method call is pending
    DWORD MessagePending(
        [in] HTASK htaskCallee,    // Called application's 
                                   //   task handle
        [in] DWORD dwTickCount,    // Elapsed tick count
        [in] DWORD dwPendingType); // Call type
}

L'implémentation d'un objet pour la méthode IMessageFilter::HandleInComingCall est appelée par COM+ lorsqu'une invocation de méthode de méthode est reçu, sachant que l'appartement a l'opportunité d'accepter, de rejeter ou de reposter l'appel. Le premier paramètre de la méthode HandleInComingCall spécifie la nature de l'appel fait par le client; qui peut être une des valeurs suivantes:

// Call types used by IMessageFilter::HandleInComingCall
typedef enum tagCALLTYPE
{
    CALLTYPE_TOPLEVEL = 1,  // Top-level call -- no outgoing call
    CALLTYPE_NESTED   = 2,  // Callback on behalf of previous 
                            //   outgoing call -- should 
                            //   always handle
    CALLTYPE_ASYNC    = 3,  // Async call -- cannot be rejected
    CALLTYPE_TOPLEVEL_CALLPENDING = 4,  // New top-level call 
                                        //   with a new logical 
                                        //   thread ID
    CALLTYPE_ASYNC_CALLPENDING    = 5   // Async call -- cannot 
                                        //   be rejected
} CALLTYPE;

Le flag CALLTYPE_NESTED indique qu'un appel réentrant est en train de se faire pendant que l'appartement est en cours d'exécution d'une autre méthode pour le même client; le flag CALLTYPE_TOPLEVEL indique qu'il n'y a aucun autre appel actif dans l'appartement. Le quatrième paramètre de la méthode IMessageFilter:: HandleInComingCall fournit un pointeur sur la structure INTERFACEINFO décrite plus bas. La structure INTERFACEINFO indique au filtre l'objet qui reçoit un appel, le IID de l'interface implémenté par cet objet, et le numéro de méthode dans cette interface.

// Additional interface information about the incoming call
typedef struct tagINTERFACEINFO
{
    IUnknown    *pUnk;      // The pointer to the object
    IID         iid;        // Interface id
    WORD        wMethod;    // Interface method
} INTERFACEINFO, *LPINTERFACEINFO;

A partir de l'information fourni, l'implémentation de la méthode IMessageFilter::HandleInComingCall du filtre de message peut retourner une des trois valeurs dans l'énumération définit ci-dessous.  La méthode doit retourner SERVERCALL_ISHANDLED si elle veut accepter l'appel, SERVERCALL_REJECTED si elle ne veut pas traiter la requête, ou SERVERCALL_RETRYLATER si l'objet ne peut actuellement pas traiter la requête mais le pourra plus tard.

// Values returned by IMessageFilter::HandleInComingCall and 
// passed to the IMessageFilter::RetryRejectedCall method on 
// the client
typedef enum tagSERVERCALL
{
    SERVERCALL_ISHANDLED    = 0, // Process the call
    SERVERCALL_REJECTED     = 1, // Reject the call
    SERVERCALL_RETRYLATER   = 2  // Tell the caller to try 
                                 //   back later
} SERVERCALL;

Si un filtre retourne SERVERCALL_REJECTED ou SERVERCALL_RETRYLATER dans la méthode IMessageFilter::HandleInComingCall, le système appelle immédiatement la méthode IMessageFilter::RetryRejectedCall sur l'implémentation client de l'interface IMessageFilter. Le troisième paramètre de la méthode RetryRejectedCall method indique la valeur retournée de la méthode HandleInComingCall par le filtre de l'objet; c'est soit SERVERCALL_REJECTED ou SERVERCALL_RETRYLATER.

Avec le flag SERVERCALL_RETRYLATER, une application client essaie à nouveau de faire des appels. Si au bout d'un certain temps, l'appel ne vient pas, l'application informe l'utilisateur. Le channel COM+ utilise la valeur retournée par la méthode IMessageFilter::RetryRejectedCall pour déterminer l'action à prendre. Un valeur de _1 indique que l'appel doit être annulé, et le proxy retourne la valeur RPC_E_CALL_REJECTED pour la méthode annulée. C'est l'action que prend le filtre par défaut de COM+. Si la méthode RetryRejectedCall retourne une valeur > à _1 mais < à 100, COM+ recommence l'appel. Une valeur >= à 100 provoque l'attente du système d'un certain nombre de millisecondes avant de recommencer l'appel.

Le système appelle la méthode IMessageFilter::MessagePending dans les applications client si un message de fenêtre apparaît da la queue de message de l'application pendant l'appel d'une méthode. Le filtre par défaut autorise certain messages (comme WM_PAINT) à être dispatchés pendant ce temps. Cela évite le gèle de l'interface utilisateur et donne l'impression que l'application n'est pas plantée. Les messages d'entrée, comme les messages souris ou clavier sont annulés pour que l'utilisateur ne fasse pas une nouvelle opération pendant la durée de l'appel. La troisième paramètre de la méthode MessagePending est une des valeurs suivantes qui indique si l'appel en cours est simple ou imbriqué.

// Pending type indicates the level of nesting
typedef enum tagPENDINGTYPE
{
    PENDINGTYPE_TOPLEVEL = 1, // Top-level call
    PENDINGTYPE_NESTED   = 2  // Nested call
} PENDINGTYPE;

La méthode IMessageFilter::MessagePending doit retourner une des valeurs de l'enumération PENDINGMSG. Retourner la valeur PENDINGMSG_CANCELCALL annule l'appel et la méthode désirée retourne RPC_E_CALL_CANCELLED. PENDINGMSG_WAITDEFPROCESS provoque le traitement du message une fois l'appel en cours terminé.  PENDINGMSG_WAITNOPROCESS annule le message et attend le retour de la méthode en cours. Notez que le filtre de l'appartement du client peut afficher un boite de dialogue d'attente à l'utilisateur via la fonction OleUIBusy.

// Return values of MessagePending
typedef enum tagPENDINGMSG
{
    PENDINGMSG_CANCELCALL = 0,      // Cancel the outgoing call
    PENDINGMSG_WAITNOPROCESS = 1,   // Wait for the return and 
                                    //   don't dispatch the message
    PENDINGMSG_WAITDEFPROCESS = 2   // Wait and dispatch the message
} PENDINGMSG;

Les Appartements Multithread

un thread entre dans le modèle MTA en appelant CoInitializeEx(NULL, COINIT_MULTITHREADED), comme dans le code ci-dessous. Un seul MTA est crée dans un processus, donc le premier appel à CoInitializeEx avec le flag COINIT_MULTITHREADED créé le MTA. Chaque nouveau thread qui fait appel à la fonction CoInitializeEx avec ce flag rejoint le MTA existant.

// Enter the MTA.
CoInitializeEx(NULL, COINIT_MULTITHREADED);

Les threads qui s'exécutent dans le MTA n'ont pas besoin de récupérer et de dispatcher des messages de fenêtre car COM+ n'utilisent les messages pour faire les appels de méthodes dans le MTA.  Les appels sont faits directement au travers de la v-table, mais COM+ n'impose aucune synchronisation aux objets du MTA. Cependant, ces objets doivent fournir leurs propres synchronisations via les critical sections, les events, les mutex, les sémaphores, ou tout autre mécanisme pour leurs besoins.

Plusieurs clients peuvent simultanément exécuter une méthode d'un objet depuis différents threads , et ces threads du MTA peuvent ne pas utiliser le TLS pour autant. Un objet dans un MTA reçoit les appels au travers d'un pool de threads COM+ appartenant au processus de l'objet. Le système créé ces threads à l'exécution et les réutilise à sa guise. Du fait que le système lance automatiquement des threads pour gérer les accès concurrents au composant, vous devriez éviter d'appeler la fonction Win32 CreateThread. En fait, appeler CreateThread interfère avec l'algorithme du pool de threads. Un outil comme Process Viewer (pview.exe), fourni avec Visual C++, permet de visualiser les threads créés dynamiquement par COM+.

Marshaling de Pointeurs d'Interface Entre Appartements

Les applications client associent les threads aux appartements via la fonction CoInitializeEx. Un objet instancié par un thread devient membre de l'appartement dans lequel réside ce thread. Le client peut appeler la fonction CreateThread pour lancer de nouveaux threads, et chaque thread qui veut accéder aux objets COM+ doit déclarer son type d'appartement en appelant CoInitializeEx. Le code suivant montre comment un thread dans un processus client créé un STA et instancie une coclasse:

void __stdcall ThreadRoutine(void)
{
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    IUnknown* pUnknown;
    HRESULT hr = CoCreateInstance(CLSID_InsideCOM, NULL, 
        CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnknown);

    pUnknown->Release();
    CoUninitialize();
}

Le pointeur IUnknown retourné par l'appel à CoCreateInstance ne peut être utilisé que dans cet appartement.  Par exemple, si le pointeur est passé par une variable globale à un thread d'un autre appartement et ensuite utilisé pour appeler une méthode, une erreur RPC_E_WRONG_THREAD se produit. Cette erreur est retournée par l'interface proxy si le client essaie d'utiliser un pointeur d'interface qui n'a pas été marshallé pour être utilisé dans un appartement différent.

Rappelez vous que les pointeurs d'interface dans COM+ sont propres à un appartement—ils ne peuvent pas être utilisés par un thread dans un autre appartement sans avoir pris des précautions spéciales. Si COM+ avait permis le partage pointeurs d'interface véritables (raw), il n'aurait aucun moyen de garantir la synchronisation requise par les différents modèles d'appartement car un objet pourrait être appelé directement depuis d'autres appartements sur d'autres threads. A la place, le système propose les fonctions CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream pour permettre le marshaling d'un pointeur d'interface d'un appartement vers un autre. CoMarshalInterThreadInterfaceInStream effectue le marshalling de l représentation neutre d'un pointeur d'interface dans un objet stream; sa déclaration est la suivante:

WINOLEAPI CoMarshalInterThreadInterfaceInStream(IN REFIID riid, 
    IN LPUNKNOWN pUnk, OUT LPSTREAM *ppStm);

Supposons qu'un thread STA1 d'un STA veut passer un pointeur d'interface au thread d'un autre STA, STA2. STA1 appelle en premier CoMarshalInterThreadInterfaceInStream pour obtenir une représentation d'interface neutre (apartment-neutral interface représentation) stockée dans un objet stream. Le premier paramètre de CoMarshalInterThreadInterfaceInStream est le IID de l'interface marshallée, et le second paramètre est un pointeur sur l'objet qui implémente cette interface. Le pointeur IStream retourné par le troisième paramètre peut alors être stocké dans une variable globale accessible à STA2. STA2 passe ce pointeur IStream à la fonction CoGetInterfaceAndReleaseStream pour unmarshaller le stream et obtenir un pointeur d'interface relatif vers le proxy qui est utilisé pour l'accès à STA2. Tout appel fait par le thread STA2 à l'objet dans STA1 est maintenant réalisé au travers du proxy chargé dans STA2 lorsque le pointeur d'interface a été unmarshallé, comme dans la Figure 4-3. La déclaration de le fonction CoGetInterfaceAndReleaseStream est la suivante:

WINOLEAPI CoGetInterfaceAndReleaseStream(IN LPSTREAM pStm, 
    IN REFIID iid, OUT LPVOID FAR* ppv);

Figure 4-3. Le thread d'un STA passant un pointeur d'interface à un autre STA.

Comme sur le code ci-dessous, le thread dans STA1 instancie un objet COM+ et appele CoMarshalInterThreadInterfaceInStream pour marshaller ce pointeur d'interface en une représentation neutre. Le pointeur IStream résultat est passé en argument au nouveau thread. STA1 peut continuer à utiliser son pointeur d'interface, et lorsqu'il a fini, il peut libérer le pointeur sans danger. Notez que le pointeur IStream n'est pas libéré dans le code ci-dessous; il est libéré par le thread qui le reçoit.

// STA1

IMyInterface* pMyInterface;
hr = CoCreateInstance(CLSID_MyCOMClass, NULL, 
    CLSCTX_LOCAL_SERVER, IID_IMyInterface, 
    (void**)&pMyInterface);

// Marshal interface pointer to stream.
IStream* pStream;
hr = CoMarshalInterThreadInterfaceInStream(IID_IMyInterface, 
    pMyInterface, &pStream);

// Spawn new thread;
// pass pStream to new thread.
DWORD threadId;
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadRoutine, 
    pStream, 0, &threadId);

// Do other work...

pMyInterface->Release();

Le nouveau thread commence son exécution à la fonction ThreadRoutine. Son seul argument est le pointeur IStream qui contient le pointeur d'interface marshallé vers l'objet qu'il veut appeler. Ce code créé un nouveau STA, STA2, en appelant CoInitializeEx, et il unmarshalle l'objet stream en appelant CoGetInterfaceAndReleaseStream. Cela a pour effet de retourner le pointeur d'interface relatif utilisable par STA2 et de libérer automatiquement le stream. Lorsqu'il a fini d'utiliser le pointeur d'interface, STA2 doit le libérer. Tout appel de méthode sur le pointeur d'interface retourné par CoGetInterfaceAndReleaseStream est marshallé en retour à l'objet actuel dans STA1 par le proxy chargé dans STA2.

void __stdcall ThreadRoutine(IStream* pStream)
{
    // Create STA2.
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // Unmarshal interface pointer from stream.
    IMyInterface* pMyInterface;
    hr = CoGetInterfaceAndReleaseStream(pStream, 
        IID_IMyInterface, (void**)&pMyInterface);
     // Do work with pMyInterface...

    pMyInterface->Release();

    CoUninitialize();
}

Les fonctions CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream sont des wrappers pour les fonctions CoMarshalInterface et CoUnmarshalInterface. La fonction CoMarshalInterThreadInterfaceInStream appele CoMarshalInterface avec le flag MSHCTX_INPROC. Ce flag a été ajouté à CoMarshalInterface  pour indiquer que le unmarshalling ce réalisé dans un autre appartement du même processus. Le pseudo-code ci-dessous détaille les fonctions CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream:

HRESULT MyMarshalInterThreadInterfaceInStream(REFIID riid, 
    IUnknown* pUnknown, IStream** pStream)
{
    CreateStreamOnHGlobal(NULL, TRUE, pStream);
    return CoMarshalInterface(*pStream, riid, pUnknown, 
        MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL);
}

HRESULT MyGetInterfaceAndReleaseStream(IStream* pStream, 
    REFIID riid, void** ppv)
{
    HRESULT hr = CoUnmarshalInterface(pStream, riid, ppv);
    pStream->Release();
    return hr;
}

Passage de Pointeurd d'Interface Entre Threads dans un MTA

Comme dans la Figure 4-4, les pointeurs d'interface peuvent être passés directement entre les threads du MTA; ils n'ont pas besoin d'être marshallé avec les fonctions CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream. Bien sûr, chaque passage d'un pointeur d'interface d'un thread MTA à un thread STA doit être marshallé avec les fonctions CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream. De plus, les filtres qui implémentent l'interface IMessageFilter ne peuvent pas être utilisés dans le modèle MTA.

Figure 4-4. Pointeurs d'interface passés directement entre les threads dans un MTA.

Comment Choisir un Modèle de Threading

Quel modèle de threading choisir ? Les threads qui ont une interface graphique doivent opter pour le modèle STA, tandis que les composants sans GUI doivent opter pour le modèle MTA. Rappelez vous que dans le modèle STA, les queues de message de fenêtre sont utilisées pour invoquer les méthodes des objets COM+. Une application GUI possède une fenêtre avec une boucle de messages  donc le modèle STA convient naturellement. Vous pouvez utiliser une combinaison de STAs, un NA et un MTA dans un même processus lorsque vous avez besoin de modèles de threads différents pour une même application. La table suivante compare les différents modèles, STAs, MTAs, et NAs.

Si vous concevez un composant sans interface graphique, le modèle MTA n'est pas forcément le modèle de à choisir obligatoirement. Ce modèle est plus rapide parce que le système n'a pas besoin de synchroniser les appels entre les threads. Lorsque vous utilisez le modèle MTA, l'objet doit implémenter son propre mécanisme de synchronisation pour les données partagées entre les threads.

FonctionnalitéSTAMTANA
Synchronisation fourni par COM+ Oui Non Non
Peut avoir plusieurs threads dans un appartement Non Oui Oui
Doit marshaller les pointeurs d'interface entre les threads d'appartements différents Oui Oui Oui
Doit marshaller les pointeurs d'interface entre les threads dans le même appartement Pas applicable; un STA n'a qu'un seul thread. Non Pas applicable; le NA n'a aucun thread résidant.
Utilise les queues de messages de fenêtre Oui Non Ca dépend. Oui si un thread STA est exécuté dans le NA; non si il est exécuté dans un thread MTA.
Doit appeler CoInitializeEx dans chaque thread qui utilise les services COM+ Oui Oui Pas applicable; il n'y a que des threads STA et MTA.
Les appels in-process sont toujours invoqués dans le thread de l'appelant Non Non (à moins que l'objet ne fasse supporte le Free-Threaded Marshaler). Oui
Peut utiliser le TLS Oui Non Non

Si vous ne savez pas comment résoudre les problèmes de synchronisation, utilisez le modèle STA. Dans ce modèle, COM+ synchronise les accès à un composant au niveau méthode—cela assure que deux threads ne peuvent pas faire un appel dans un STA en même temps. Vous pouvez résoudre ce comportement dans le modèle MTA en utilisant une section critique, comme dans le code ci-dessous. Cependant, au lieu de construire des blocks de verrous sur chaque méthode comme le fait le modèle STA, un composant qui supporte le modèle MTA doit utiliser les verrous seulement sur de petites portions de code, là ou les données partagées sont susceptibles d'être accédées par plusieurs threads en même temps.

class CMyClass
{
public:
    CMyClass::CMyClass()
    {
        // Create the critical section.
        InitializeCriticalSection(&m_cs);
    }

    CMyClass::~CMyClass()
    {
        // Before exiting, free the critical section.
        DeleteCriticalSection(&m_cs);
    }

    HRESULT __stdcall CMyClass::MyMethod(int MyParameter)
    {
        // Verify that no other method in the object is 
        // being called.
        EnterCriticalSection(&m_cs);

        // Write your code here...

        // Leave the critical section, enabling another method 
        // to execute. 
        LeaveCriticalSection(&m_cs);
        return S_OK;
    }

    HRESULT __stdcall CMyClass::MyOtherMethod(int MyParameter)
    {
        EnterCriticalSection(&m_cs);

        // Write your code here...

        LeaveCriticalSection(&m_cs);
        return S_OK;
    }

private:
    // The critical section data structure
    CRITICAL_SECTION m_cs;
};