Details about com single-threaded suites
1 Introduction
Advanced com projects often need to pass objects across threads to call these object methods in different threads to stimulate their events. The following article is intended for junior com developers who have basic com knowledge (such as understanding the iunkown and idispatch interfaces. For more information about the com suite, please visit! The com suite is a special topic worth learning and understanding. However, in order to improve the reader's com suite, this article only describes the single-threaded suite in two parts. The first part focuses on the theory and understanding of the basic architecture of STAS (single-threaded suites), and the second part is to deepen the understanding of Single-threaded suites through numerous complex examples. In this article, I will illustrate how to call object methods and stimulate object events across threads one by one. In addition, the single-threaded suite is selected because it is widely used and does not require complicated synchronization mechanisms in thread-Safe applications. The framework of this article is as follows:
Com suite
This article briefly explains the concept, purpose, and reason of the COM suite. The relationship between the suite, thread, and COM object is also discussed, which shows how the internal process and object of the suite depend on each other.
Single-threaded Suite (STA)
This part is the focus of this article, in order to deepen the reader's learning of a single thread suite. In this section, we will see the threads under the Single-threaded suite rule and learn how com fully uses the old message loop. Before discussing how to implement the COM Object and thread of a Single-threaded suite, this article lists the advantages and disadvantages of a Single-threaded suite.
Exe com server and suite
The last part of this article mainly describes the relationship between the exe com server and the suite. As we all know, there are some important differences between the DLL server and the EXE server. We also hope that this part will make readers aware of the importance of the class factory. To illustrate important concepts, I wrote the example source code.
Come on, enjoy this article together!
Ii. Explanation
Com suite
To understand how com processes threads, you must master the concept of a suite! A suite is a logical container in an application, so that COM objects can share and follow the same thread access rules (for example, in the thread where they belong, the rule limits how object methods and attributes are called in the case of a suite or no suite ). A suite is essentially a concept, unlike an object that has its own attributes and methods. No handle type can be referenced, and no callable API can be used to manipulate it. For new users, this may be an important reason why the suite is hard to understand. It is so abstract.
If cocreateapartment () and some API functions such as coenterapartment () exist, the suite may be easy to understand if Microsoft provides com classes with iapartment interfaces and manipulating threads and object methods. From programming, it seems that there is no practical way to gain insight into the suite. To help beginners overcome the difficulties they encounter when learning suites, the following references are provided:
1. A suite is created by an application. No functions are directly created or checked.
2. threads and objects also enter the suite through applications and participate in activities related to the suite. no ready-made functions are available to complete this process.
3. The Suite model is very similar to a protocol or a set of rules to be followed.
In the operating system where multiple threads access many COM objects, how can we ensure that the calling result of a thread on the COM Object attribute and method is not affected by the access of another thread to this object? To solve the above problems, the emergence of COM suite is to ensure the so-called thread security. Through the suite, we can ensure that the internal state of the object accessed by this thread is not affected by access by other threads.
Com contains three suite models: Single-threaded suite, multi-threaded suite, and neutral Suite (neutral apartment). Each type of suite represents a mechanism to synchronize the internal state of objects across threads. For threads and objects, the suite follows the following basic principles:
1. a COM object only exists in one suite. The runtime object is determined as soon as it is created and remains in the suite until it is destroyed.
2. A com thread (a thread that creates a COM object or calls a com method internally) also belongs to a suite. Like a COM object, a thread exists in the same suite from creation to completion.
3. the threads and objects in the same suite follow the same thread access rules. The internal method of the suite is called directly without the additional assistance of COM.
4. threads and objects in different suites follow different thread access rules. Cross-Suite method calling is implemented through the column set, which requires proxies and stubs.
In addition to ensuring thread security, The other advantage of the suite is transparency. The object and client do not need to care about the suite model they adopt. The Implementation Details of the underlying Suite (especially the column Set Mechanism) are managed by the COM subsystem, so developers do not need to worry about it.
(2) set the COM Object suite Model
From the beginning of this section to "exe com server and suite", the COM objects involved in this period are all implemented in the DLL server. As mentioned above, the COM object only belongs to a running suite and is determined when the object is created. However, the first thing to understand is how a COM object is associated with its suite model? For the com class in the DLL server, when the com thread instantiates it, the class will refer to the string value of the registry "inprocserver32" field item "threadingmodel. This setting is under developer control. For example, when you use ATL to develop a COM object, you can specify the thread model used by the object at runtime. The following lists the thread model string values and the Representative suite model:
No. registry value suite Model
1 "apartment" Single-threaded suite
2 "single" or empty legacy single-threaded suite
3 "free" multi-threaded suite
4 "neutral" neutral suite
5. "both" creates the thread's suite model.
The "both" string value indicates that the COM object is applicable to single-threaded and multi-threaded suites.
(3) set the com thread suite Model
Each com thread must initialize itself through the coinitializeex () function and set the parameter to coinit_apartmentthreaded or coinit_multithreaded. The thread accessing the coinitializeex () function is a com thread, that is, the thread has entered the suite. Leave the suite until the thread calls the couninitialize () function or terminates itself.
Single-threaded suite
A single-threaded suite can only contain one thread (thus called a single-threaded suite), but can contain multiple objects. A thread contained in a single-threaded suite has a special feature. If an object in a thread needs to be exported to another thread, it must have its own message loop. By calling coinitializeex () and specifying the function parameter as coinit_apartmentthreaded or calling the coinitizlize () function (coinitialize (), the thread actually calls coinitializeex () with the parameter set to coinit_apartmentthreaded ()) enter the single-threaded suite. The thread that enters the single-threaded suite can also think that the suite has been created (after all, there are no other threads in the suite to create it for the first time ). By specifying an "apartment" to the appropriate location in the Registry and instantiating an object in a single-threaded suite, we can say that the COM object enters the single-threaded suite.
(1) stathread access rules
The STS thread access rules are as follows:
1. All sat objects, such as the stathread, belong to the same single-threaded suite.
2. All objects in the sta only accept the method call of the stathread.
The first point is very natural and simple to understand. However, note that the two objects of the same com class created in the same DLL server and in different stathreads belong to different suites. To access these two objects, enter a cross-Suite object, which must be completed through the com column set. As for the second point, there are two ways to access the sta object: 1. Internal access to the stathread. In this case, the call of the method is serialized. 2. Cross-thread access (that is, cross-Suite access ). In this case, the stathread must contain a message loop, so that com can ensure that the object only accepts the method access of its own stathread.
(2) message loop in sta
A thread with a message loop is a UI Thread associated with one or more windows, that is, the thread has these windows. The corresponding Window Process is created by the corresponding thread. Any thread sends or publishes messages to all windows, but only the target window process responds to these messages. Messages sent to the target window are synchronized, that is, the window ensures that messages are processed in the order of message sending or publishing. For Windows program developers, the window processing process does not have to be thread-safe. Each window message is converted into an atomic operation. The next message can be processed only after the current message is processed. In Windows, this gives com inherent advantages: Making the COM Object easy to implement thread security. Com publishes a private message to a hidden window associated with the object to call the sta object method of an external suite. The Window Process of the hidden window arranges to process object access and return the result to the caller.
When an external suite is involved, com always introduces porxies and stubs. Like a message loop, both form part of the single-threaded suite protocol. There are two important knowledge points to pay attention:
1. The system can only use the message loop to stimulate the sta object when accessing the external suite. For access from the suite, no com participation is required. The stathread itself will ensure that the call is executed in serial mode.
2. If the stathread fails to obtain or distribute messages from the message queue, the COM object in the thread suite cannot be accessed between the suites.
Taking into account the above 2nd points, such as sleep (), waitforsingleobject (), waitformultipleobjects () and other functions that affect the thread message processing sequence are very important. If the stathread needs to wait for the synchronization object, special processing must be taken to ensure that the message loop is not disrupted.
In some cases, the stathread does not need to contain a message loop.
(3) Advantages and Disadvantages of sta
The biggest advantage of using sta is simplicity. For the COM Object Server, except for some basic code, the COM Object and thread involved almost do not need to synchronize the code. The call of all methods in COM is automatically serialized. Because the sta object is always accessed by the same thread, it has thread similarity. It is the thread similarity that allows Stas object developers to use threads to locally store the state of internal data of objects. VB and MFC use this development technology, so the objects they develop are single-threaded suite objects. To support the legacy COM component, it is inevitable to use the STA.
Everything has two sides. Of course, using sta is no exception. When multiple threads access the same COM object, the sta architecture seriously reduces the performance. Since each thread accesses objects in a serialized manner, the thread must wait for the return of other occupied threads. The wait time reduces the application response or performance. If the thread contains too many objects, the sta architecture also reduces the performance. Remember that a single-threaded suite contains one thread and one message queue. Therefore, the access to each object in the sta is serialized by the message queue.
Considering the advantages and disadvantages of STA, You must select it based on the actual application scenario.
(4) Implementation of the sta COM object and its server
For developers, the implementation of the sta COM Object generally does not need to care about the serialized access to internal member data. However, the single-threaded suite itself cannot ensure the global data of the com dll server and the security of the exported global functions (such as dllgetclassobject () and dllcanunloadnow () threads. Remember that the COM server object can be created in any thread, and the two objects in the same DLL server can be created in their own stathread, this ensures that the global data and functions of the server without the need for the serial mechanism of COM can be correctly accessed by two different threads.
In addition, in this case, the message loop of the thread cannot provide any help. After all, this is not the internal state of the object (if it is a program, it will have to pay the price), but the internal state of the server. Because objects of different threads may access global variables and global functions of the server, they need to be serialized as appropriate. This rule also applies to static member functions and static member variables of the class.
A well-known COM server variable is a global object count, which is called by common global functions dllgetclassobject () and dllcanunloadnow. The API functions interlockedincrement () and interlockeddecrement () can synchronize access to Global Object counts by different threads.
Summarize the general implementation principles of the sta Server DLL:
1. The Server DLL must have standard thread-safe entry functions, such as dllgetclassobject () and dllcanunloadnow ().
2. The private global functions of Server DLL must be thread-safe.
3. Private global variables (especially global object counts) must be thread-safe.
The dllgetclassobject () function provides Class Object Access. Class Object is returned Based on CLSID and referenced by its interface (usually iclassfactory) pointer. The dllgetclassobject () function is called within the cogetclassobject () API function. com developers do not need to call it directly. By using the CLSID of the Class Object to create an object instance (by iclassfactory: createinstance (), we can regard the dllgetclassobject () function as a switch for creating a COM object, remember the most important thing about this function: it affects the Global Object count. We call the dllgetclassobject () function to identify whether or not the COM Service dll contains the object and its function. The dllgetclassobject () function determines the return value based on the Global Object count. If no object exists in the COM Server DLL, you can call this function to uninstall the COM Server DLL from the memory.
The functions dllgetclassobject () and dllcanunloadnow () must be thread-safe to synchronize the Global Object count. Generally, when an object is created or destroyed, the Global Object count increases or decreases accordingly. This article does not detail the thread security of private global functions and global variables. It can only be discussed by experts and experienced users.
For the COM server, ensuring thread security is not a complicated process. In many cases, you only need to think about it. The above explanations are sufficient for atl com server developers (except for the thread security of private global data and global functions), so they only need to focus on the business logic development of COM objects.
(5) implementation of the stathread
The stathread initializes itself by calling coinitialize () or coinitializeex (coinit_apartmentthreaded. Second, if the object created by the thread needs to be exported to other threads (that is, other suites), the thread needs to provide a message loop to process messages in the hidden window of the COM object. Remember to accept and process these private messages in the hidden Window Process of COM. The stathread itself will not process them. The following code demonstrates the stathread framework:
DWORD winapi threadproc (lpvoid lpvparamater) {/* initialize COM and declare this thread to be an sta thread. */: coinitialize (null );......... /* The message loop of the thread. */MSG; while (getmessage (& MSG, null) {translatemessage (& MSG );
Dispatchmessage (& MSG) ;}:: couninitialize (); Return 0 ;}
The stathread is very similar to the winmain () function. In fact, the winmain () function of Windows applications also runs the thread. You can implement a stathread like the winmain () function, that is, create a window before the message loop and ensure that the window has a suitable window process. Of course, you can create and manage COM objects in the window, or access external Stas objects across suites. If you do not want to create a window in the thread, you can still create methods to manipulate objects and access external threads across suites.
(6) stathread without message loop
The stathread sometimes does not require a message loop. For example, a thread only creates and uses objects and does not provide access to other suites. Example:
Int main () {: coinitialize (null); if (1) {isimplecomobject1ptr spisimplecomobject1; values (_ uuidof (simplecomobject1); spisimplecomobject1-> initialize (); spisimplecomobject1-> uninitialize () ;}:: couninitialize (); Return 0;
}
The console program above demonstrates how to create a STA in the main thread without a message loop. Note that we can successfully call the initialize () and uninitialize () functions, because they are called within the STA, and do not involve the clustering and message loop. However, if we call the: coinitializeex (null, coinit_multithreaded) function, the main () function becomes a multi-threaded suite, and the program has the following changes:
1. The call of the initialize () and uninitialize () functions requires the assistance of the COM column set.
2. the COM Object spisimplecomobject1 belongs to the default sta suite created by the COM subsystem rather than the MTA suite.
3. The main () thread itself does not require a message loop, but is required by the default Sta.
4. Call the initialize () function and uninitialize () function requires the message to participate in the cycle.
Note that if the stathread requires a message loop, ensure that the message loop is stable without interruption.
(7) example focus on sta
Next we will focus on the single-threaded suite. The method used is to observe the ID of the execution thread when the COM Object method is called. For standard Stas objects, this ID is the ID of the Stas object. If an sta object does not belong to the thread that created it (that is, this thread is not a stathread), the thread ID does not match the thread ID of the execution object method.
Standard sta
A simple example is provided to illustrate the standard Sta. The following example shows a simple Stas COM object. The corresponding com class csimplecomobject2 implements the testmethod1 () method. Testmethod1 () shows the ID message box of the executing thread. The Code is as follows:
Stdmethodimp csimplecomobject2: testmethod1 () {tchar szmessage [256]; sprintf (szmessage, "thread ID: 0x % x", getcurrentthreadid ();: MessageBox (null, szmessage, "testmethod1 ()", mb_ OK); Return s_ OK ;}
We instantiate csimplecomobject2 through a simple test program and call the testmethod1 method. The Code is as follows:
Int main () {handle hthread = NULL; DWORD dwthreadid = 0;: coinitializeex (null, coinit_apartmentthreaded); displaycurrentthreadid (); if (1) {isimplecomobject2ptr spisimplecomobject2; spisimplecomobject2.createinstance (_ uuidof (simplecomobject2); spisimplecomobject2
-> Testmethod1 (); hthread = createthread (lpsecurity_attributes) null, // SD (size_t) 0, // initial stack size (lpthread_start_routine) threadfunc, // thread function (lpvoid) null, // thread argument (DWORD) 0, // Creation
Option (lpdword) & dwthreadid // thread identifier); waitforsingleobject (hthread, infinite); spisimplecomobject2-> testmethod1 () ;}: couninitialize (); Return 0 ;}
The threadfunc () function of the thread function is as follows:
DWORD winapi threadfunc (lpvoid lpvparameter) {: coinitializeex (null, coinit_apartmentthreaded); displaycurrentthreadid (); if (1) {isimplecomobject2ptr handle; Response Parameter; parameter. createinstance (_ uuidof (simplecomobject2 ));
Spisimplecomobject2b. createinstance (_ uuidof (simplecomobject2); spisimplecomobject2a-> testmethod1 (); parameters-> testmethod1 () ;}:: couninitialize (); Return 0 ;}
The thread function contains the displaycurrentthreadid () function that displays the message box of the current thread ID. The Code is as follows:
/* Simple function that displays the current thread ID. */void displaycurrentthreadid () {tchar szmessage [256]; sprintf (szmessage, "thread ID: 0x % x", getcurrentthreadid ();: MessageBox (null, szmessage, "testmethod1 ()", mb_ OK );}
In the preceding example, a single-threaded suite is created, and the thread ID is used to explain this process. From the main () function, detailed analysis is as follows:
1. The main () function calls coinitializeex and the coinit_apartmentthreaded parameter to enter the single-threaded suite. The STA object created in main () is part of the main () thread.
2. The main () function calls the displaycurrentthreadid () function. The ID of the main () thread is displayed and set to thread_id_1.
3. The program then instantiated the com class simplecomobject2. The object and the main () thread belong to the same Sta.
4. The thread ID displayed by the spisimplecomobject2 method testmethod1 () must be thread_id_1. Then the thread and thread function threadfunc () are started, and the main program calls waitforsingleobject () to wait until the end of the thread function threadfunc.
5. The threadfunc () function calls coinitializeex and the coinit_apartmentthreaded parameter again and enters the single-threaded suite. Note that the sta suite is newly created, which is different from the STA of the main () function.
6. The displaycurrentthreadid () function is called in the thread function. The ID of the threadfunc () thread is displayed and set to thread_id_2.
7. Then we created two COM objects (spisimplecomobject2a and spisimplecomobject2b ).
8. The thread function calls the testmethod1 () method of two objects ().
9. The row thread ID is displayed when the objects spisimplecomobject2a and spisimplecomobject2b call their respective methods.
10. They display the same thread ID as threadfunc (), that is, thread_id_2.
11. put back the main program after the thread ends.
12. The main program calls the method testmethod1 () again. Of course, the thread ID displayed is still thread_id_1.
For example, you must remember that different instances of the same com class can belong to different independent Stas. For a standard sta model, it is important that the suite instantiates the sta object. This example does not need to provide any message cycle, because each object is in their own suite and does not have cross-thread calls. Even if we call waitforsingleobject () in main (), there will be no trouble.
Default sta
What if the sta object is created in a non-stathread? The following code demonstrates this situation. The com class simplecomobject2 and the function displaycurrentthreadid () are the same as the first example.
Int main () {: coinitializeex (null, coinit_multithreaded); displaycurrentthreadid (); if (1) {isimplecomobject2ptr spisimplecomobject2;/* if a default sta is to be created and used, it will be created * // * right after spisimplecomobject2 (an sta object) is
Created. */spisimplecomobject2.createinstance (_ uuidof (simplecomobject2); spisimplecomobject2-> testmethod1 (); }:: couninitialize (); Return 0 ;}
The detailed execution process of the program is as follows:
1. The main () function calls coinitializeex (null, coinit_multithreaded), and the main thread initializes itself as a multi-threaded suite.
2. The program then calls the displaycurrentthreadid () function and displays the ID of the main () thread.
3. The STA object spisimplecomobject2 is instantiated in the thread.
4. Note that the object spisimplecomobject2 is initialized in a non-stathread. It does not belong to the mta of the main () thread and belongs to the default Sta.
5. Call the method testmethod1 () of the spisimplecomobject2 object. The displayed thread ID is not the main () thread ID.
When all sta objects are created in a non-stathread, they are automatically assigned to a default STA, which is created when an object is created. In this case, the object proxy rather than the pointer is obtained in the thread where the object is created. Note that the default sta must contain a message loop provided by COM.
As a newcomer to the com suite world, you must note that even if you access the createinstance or cocreateinstance function, the generated object may be instantiated in another thread. Com is completely transparent to users. Pay attention to these small details, especially during debugging.
Legacy sta
Legacy sta belongs to the default single-threaded suite, and its object belongs to the legacy COM object. Legacy means that those components do not have any thread knowledge. These components must have the threadingmodel registry set to "single" or other registries. What is important about the legacy sta object is that the instances of these objects must be created in the same STA and they always exist and run in this legacy STA, even if they are created in :: coinitializeex (null, coinit_apartmentthreaded) in the initialization thread.
Legacy sta is usually the first single-threaded suite in a process. If a legacy sta object is created before any sta model, a legacy sta is automatically created by the COM subsystem. The benefit of developing a legacy sta object is that the access to these object instances will be serialized, and calls between any two legacy sta objects will not involve a column set. However, non-Legacy sta objects must access the legacy sta object through the inter-apartment column set, and vice versa. I think this is not an attractive advantage.
In addition, all legacy sta objects must be created only in the same stathread.
Exe com server and suite
After discussing the dll com server, we will introduce the exe com server for the sake of article integrity. First, we will introduce two important differences between the DLL server and the EXE server.
Difference 1: How objects are created
When creating a COM object defined in the DLL in COM, you must install the DLL and call the export function dllgetclassobject () to obtain the iclassfactory interface pointer of the COM Object factory class. The EXE server actually follows this process: obtains the iclassfactory interface pointer of the COM Object factory class and creates an object through it. What is the difference between the DLL server and the EXE server?
The DLL server needs to export the function dllgetclassobject () so that com can extract the class factory. The EXE server does not need to export any function, but needs to register the class factory with the com subsystem at startup and destroy it upon exit. This registration process is implemented by calling the coregisterclassobject () API function.
Difference 2: Method of object suite model declaration
As mentioned above, objects in the DLL server declare their suites by setting the "threadingmodel" string entry in the "inprocserver2" registry. You do not need to set the registry for objects in the EXE server. The Suite model of the threads that register the object factory determines the object suite model.
In addition to the above two differences, the reader should also note that the sta object in the DLL server may only serve the calls within the thread, the client calls all methods of the EXE Server Object in a cross-thread manner. This process requires the use of the column set and stubs, and the message loop of the object's suite thread.
Reference books:
The essence of COM, a programmer's Workbook (3rd edition) by David S. Platt. Published by Prentice Hall PTR.
Inside COM by Dale Rogerson. Published by Microsoft press.
Essential COM by Don box. Published by Addison-Wesley.