Establish multithreaded COM server with C Builder
Sunspot Lee
First, thread, Apartment and processes
Say the COM thread model, everyone will think of a variety of Apartment models. But what is Apartment? How to build an Apartment?
Apartment is the container of the thread, and the operation in the thread must be performed in Apartment. Apartment is divided into STA and MTA, and STA is a container that can only accommodate a thread, and MTA is a container that can accommodate multiple threads. COM regulations, there can be multiple STAs in a process, but only one MTA can be available. After the thread calls CoinitializeEx (Null, COINIT_APARTMENTTHREADED), this thread is established and enters a STA, and the thread comes into CoinitializeEx (Null, COINIT_MULTITHREADED), this thread enters the process public MTA. A thread cannot enter two Apartment at the same time. After the thread calls CounInitialize (), this thread exits its Apartment. The "Apartment Model" set when designing COM objects means that this COM object can stay in that Apartment. A thread created COM object is automatically stayed in the Apartment in this thread. If this thread has established a lot of COM objects, these objects are in the Apartment in this thread.
A thread can directly access the COM object in the Apartment it, but to access the COM object in another Apartment must be scheduled. Because there is only one thread in STA, other threads want to access the COM object established by this thread, you must make this thread to work, so, for all COM objects in this Apartment are serialized, these COM objects Don't worry that there are several threads to access its trouble. The COM object in MTA is not so comfortable, and they must consider that there may be several threads to access them simultaneously. One thread other than MTA accesses a COM object in the MTA, and the system will take a thread from the COM system thread pool into the MTA, which is used to access this COM object on behalf of the customer thread. (How is the mechanism of COM system thread pool? How many threads in the pool?)
Second, customers and servers
The COM object is located in the server, the server is divided into three types of servers, process external servers, and remote servers. The server is a DLL file. The process external server is an EXE file, the remote server is a DLL file or EXE file on another computer. If a remote server is a DLL file, call it by a agent called "surrogate".
The Apartment Model of the COM object in the process in the process If the client thread is located, the customer thread is built directly in the Apartment of the customer thread when the COM object is established. For example, the Apartment model with the STA, Free model with the MTA, Both model with STA or MTA. This way the customer thread can call the COM object directly without scheduling. Otherwise, a thread will be established, and then create a COM object, COM objects, and customer threads are in two Apartment. The COM object in the process server and the remote server must not be built in the APARTMENT in the client thread. The call to them must be scheduled.
Third, establish a multi-Apartment process external server under C Builder
Because you don't have to consider parallel issues, COM objects are generally set to use the APARTMENT thread model. There is no problem in the process, if you try to build a process external server, and let several customers access objects in the server, they will find that these accesss are not simultaneous. If there is an access to the special fee, it will wait for a long time. This is because there is only one STA in the server, although each thread has established its own COM object, but these objects are in this STA, of course, cannot be executed in parallel. Overcoming this problem is simple, open the borland / cbuilder5 / include / atl / atlmod.h file, line 266:
Typedef Tatlmodule
Change to:
#ifdef __dll__
Typedef Tatlmodule
#ELSE
Typedef Tatlmodule
#ENDIF
Open the borland / cbuilder5 / include / atl / attlecome.h file, line 3214:
Declare_classfactory ()
Change to:
#ifdef __dll__
Declare_classfactory ()
#ELSE
Declare_classfactory_auto_thread ()
#ENDIF
Yes. Rebate your program, and drive two customers try it, is it concurrently executed?
Don't be too early, if you have five customers at the same time, and four of them are accessed at the time of implementation, you will find the fifth customer access to wait for a while. This phenomenon is related to the implementation code of C Builder.
After the previous changes, the server will pre-gently become several threads, each of which enters a STA. When the server receives the customer's access request, the specified a thread is responsible for the establishment of this customer to access the COM object.
For example, the first customer requests to create a COM object, the server gives the first line to send a message, let this thread build a COM object and pass the interface of this COM object to the customer, the first customer's access to this COM object All line proxy. The second customer's establishment of COM objects, the transaction to the COM object is specified by the server, if the customer is too much, the thread is used, and the server will let the first line responsible for the customer's request, cycle. If customers, threads may be responsible for several customers' access requirements, and access to customers of the same thread service will execute. The number of threads made by the predecence defaults to the number of CPUs in the system, which is four (unless you have several CPUs).
only at the same time, four customers can of course, let us continue to modify. Open the primary CPP file, you can see the following two lines of code:
Tcommodule ProjectModule (0);
Tcommodule & _Module = ProjectModule
Change to:
Tcommodule ProjectModule;
Tcommodule & _Module = ProjectModule
Among them, "MyInitatl Server" is a new function, defined as follows:
void __fastcall myinitatlserver ()
{
IF (_Module.saveinitProc) _Module.saveinitProc ();
_Module.init (Objectmap, sysinit :: Hinstance, Null, 6); // Note this 6
_Module.m_threadid = :: getCurrentThreadId ();
_Module.m_bautomationServer = true;
_Module.dofileAndObjectRegistration ();
AddterminateProc (_Module.AutomationterminateProc);
}
Seeing that 6 didn't, this represents the server after startup, I will prepare 6 threads, and you can also serve 6 customers at the same time. This 6 can be changed to other numbers, of course, don't be too big, otherwise the machine will not blame me.
Change to now you may be more satisfied, but in fact, this server still has a defect: Is it too wasteful to generate all threads at the beginning? The loop allocation thread is not too reasonable. More importantly, if the customer program is on, there is no Release its COM object, that COM object will always exist, the resource occupied cannot be recovered.
To solve these problems, it is more troublesome. It is recommended to take a look at the ATL source code, write your own Tcommodule class and CCOMTHREADALLOCATOR class.
Fourth, the problem should be paid attention to when writing multithreaded client
Established a customer program that must contain a good COM object packaging class in the * _tl.h file. For example, I built a Comlib server with a MyComobj object, then there is a TcomimyComobj class in the comlib_atl.h file, which has a good encapsulated myComobj object. Create it when you write a single thread program:
Tcomimycomobj Acomobj = Comycomobj :: CreateInstance ();
(ComyComobj is defined in a secondary class in the comlib_atl.h file) and then you can use Acomobj, do not need to call CoinitializeEx () and CounInitialize (), and do not have to release Acomobj. Suppose a method Fun (), a property NUM is defined in the MyComobj object, which can be used:
Accomobj.fun ();
Accomobj.num = 14;
IT VAL = Acomobj.num;
Do you notice Num access method? C Builder flexibly uses unique __property keywords, do not have to call GET_NUM () and set_num ().
If you write a multi-threaded customer program, you will have problems: In addition to the normal thread, the back thread cannot establish a COM object.
The problem is in Comycomobj, which guarantees that CoinitializeEx () and CounInitialize () and CounInitialize () will only be called once in the entire process. In multi-threaded client, each thread must call CoinitializeEx () and CounInitialize () once. Therefore, in addition to the first thread successfully entered Apartment, other threads failed.
I can build TcomiMomobj objects like this:
CoinitializeEx (NULL, COINIT_APARTMENTTHREADED);
IMycomobj * pcomobj;
OLECHECK (CLSID_MYCOMOBJ, NULL, CLSCTX_LOCAL_SERVER
, IID_IMYCOMOBJ, (Void **)))); Tcomicomobjinexe Acomobj (PCOMOBJ);
use Acomobj ...
CounInitialize ();
Note that this code must be written in TTHREAD :: Execute () because only the code in TTHREAD :: Execute () is truly running in the new thread. In addition, PCOMOBJ-> Release () cannot be called.
postscript
Later I saw the example of Delphi, I wanted to use it in C Builder, but I won't start. In the part of COM, Delphi and C Builder are too big, and there is no book in this area, and there is very few online information, but I have to explore it. During the period, I have posted a question online, I always have no one to answer, pain!