devxlogo

COM+, ErrObject, and Co.

COM+, ErrObject, and Co.

am sure you have already found out why you should be wary of static, application-global and module-global variables in COM+ components written in VB. As for me, one wonderful day I was a bit surprised at the ungrateful behavior of static variables while testing VB COM+ components under pressure. Anyway there is just a good article by Jimmy Nilsson on this topic. Have already read it? Well, let?s go further.

Some other day (but not any less wonderful) I thought what if VB ErrObject behaves as a double agent like a static or global variable? You see it is stored in TLS too. And what is about App and so on and so forth… Houston, we?ve got a problem: there are so many internal VB objects that are stored in TLS?

Confused? ? Me too.

Abstract
Here are the contents of the article mentioned above, in short. Global variables are not true global variables in VB as you might think. Every application-global, module-global or static variable is stored in TLS, local memory of the thread where the object was created in (by the way it is the main feature that prevents VB objects to be pooled, I believe).

Let?s remember the features of the thread pooling in MTS and COM+.

1. There are the following features in MTS thread pooling:

1.1. There is a default restriction to 100 threads per MTS package.

1.2.  This is configurable in the following way: HKLM/Software/Microsoft/Transaction Server/Packages/{GUID of the package goes here}, value ThreadPoolMax:DWORD.

2. The COM+ thread pooling has the following restrictions:

2.1. Initially, the thread pool size is 7 plus the number of CPU on board.

2.2. The thread pool size can be increased automatically up to 10 multiplied by the number of CPU on board.

2.3. This is configurable so:  HKLMSoftwareMicrosoftCOM3STAThreadPool, value EmulateMTSBehavior:DWORD. This possibility has been introduced in the Windows 2000 Service Pack 2 COM+ Rollup Hotfix 10.

You can see more detailed picture on reading MS KB Q282490 and Q303071.

Well, as you can see COM+ has a default amount of only 8 threads per COM+ application and CPU initially. It means we can get the following picture. Let?s take we create nine instances of the same COM+ component placed under COM+ and call its methods which in its turn are using global variables. The ninth instance of the COM+ component will be created in the thread that has been already used by another instance of this component. Therefore the new ninth instance will read and (oh, dear) write to the same global variables that are being used by some previous instance.

And now, contents of the current article. As my investigation shows, ErrObject is stored in TLS too. It means we can get that an error originated in one instance will be seen in quite different instance. Moreover App is also kept in TLS. It means we can get that some properties of App set in one instance will be seen in another one. Moreover Microsoft has never published the list of internal objects that are stored in TLS. Fancy, where it can lead us to! Are you frightened to death? First of all you should not believe me until I prove all I have said. Second, I will try to show that such architecture is not such fatal as it might seem at first sight.

Test, Simple but Nice
It will take five minutes to write the test revealing the behavior of VB internal objects under COM+.

1. Create a new project (COM DLL).

2. Name it Project1.

3. Add a module and paste in one string: Public g_lng As Long.

4. Inherit Class1 code from given below just by copy-paste technique:

Option ExplicitPublic Function Oops(ByRef er As Long, _    ByRef str As String, ByRef lng As Long) As Long First   Results
As you can see the first eight forms show Err().Number is zero, App.Title is ?Project1? and at last global variable is equal to zero. Now press command button on the ninth form and get the following results: Err().Number is 5, App.Title is ID of the thread and global variable is 1. It means the ninth instance has got to the thread when one of the previous eight forms was created in. As a rule it is the thread of the first form. The investigation of ThreadID of all created instances testifies to the usage of a simple round robin algorithm for assignment of threads by Microsoft.

Refinements
And now let?s begin to consider the refinements. It is so interesting to clear out what system objects exactly are shared between instances of different components and which ones are not.

We will find out right now:

1. Uncomment the commented section of the code in the client EXE application and recompile the application. The uncommented section introduces the instance of the second component doing the same as the first one.

2. Repeat steps 1-6 but name the new project as Project2 and new coclass as Class2.

Now that you have done it launch nine forms as in the previous case. Fire! As you can see ErrObject is being shared among instances of different components because the fifth form already has 5 as Err().Number value. App and global variables are not shared because the fifth form shows their right values.

The bottom line is:

1. ErrObject is shared between instances of components of different types.

2. Global variables and App object are not shared between instances of components which have different types.

Problem Solving...
It seems to be a pity. You can get rid of global and static variables and frankly speaking you should get rid of them (except when the effect is positive for you, such as when caching static values). But how should we get around the problem with intrinsic system objects like ErrObject and App? Should we introduce critical sections or something like that?

Solving problems first of all we should be aware whether the problem really exists. I know a lot of problems that may be solved just by a bit of healthy sleep. Theory will help us, I believe.

?and Theory
Two axioms come here:

1. Only COM DLL can be placed under COM+ so we will consider the questions of synchronization only for in-process components.

2. All in-process components created with VB use apartment-model threading to provide synchronous access to objects in multithreaded environment.

Let?s point to the main enemy of mankind in multithreaded environment at once. So the main enemy is potential reentrancy. The new call to the component may be for the member the thread was already executing - in this case the thread enters the member a second time - or it may be for another member. If the second member does not yield, it will finish processing before the first member. If it changes module-level data or something else without any critical section the first member was using, the result may be unpredictable. For these purposes there was implemented apartment-model threading in COM.

In apartment-model threading, each thread is like an apartment - all objects created on the thread execute only in this apartment, and are unaware of objects living in other apartments. So let?s consider an apartment just as a thread with some additional metainformation.

In the apartment model, reentrancy is handled in the following way:

1. An apartment?s thread of execution enters an object?s code, because a property or method has been invoked.

2. While the thread is in the property or method, another thread invokes a property or method of the object, and COM serializes this request. (It just places the request as next event to the standard window message queue. Each apartment has window message pumping loop handling all incoming events.) That is, it queues the request until the thread that owns the object?s apartment finishes the member it is currently executing.

3. Before the thread reaches the end of the member, it executes code that yields control of the processor.

4. COM tells the thread to begin executing the serialized request, so that the thread reenters the object?s code.

By serializing method calls for each apartment, COM protects you from reentrancy - unless your code yields control of the processor. Ways in which your code can yield control of the processor may be for instance such as: raising an event that is handled by an object on another thread, or in another process.

VB implementation of apartment-model threading eliminates conflicts in accessing global data from multiple threads by giving each apartment its own copy of global data. A separate copy of all global data such as variables in modules and static variables is maintained for each apartment. In addition to maintaining a separate copy of global data, a Sub Main procedure is executed for each new apartment (that is, for each new thread). Otherwise, there would be no way to initialize global data for the new thread. The first time a client thread requests an object provided by your VB COM DLL, a new apartment is created, and Sub Main executes for that apartment.

In-process components created with VB can be single-threaded or multithreaded. A single-threaded component has only one apartment, which contains all the objects the component provides. The client thread that handles all the calls to objects in the single-threaded in-process component will be the first thread that called CoInitializeEx function. This means that a single-threaded DLL created with Visual Basic is safe to use with a multithreaded client. However, there is a performance trade-off for this safety. Calls from all client threads except one are marshaled, just as if they were out-of-process calls.

A multithreaded component can have as many apartments as there are threads of the client that create instances of the component. Multithreaded COM DLL component provides the following benefits:

1. All of the objects a client creates on a given thread will be created in the same apartment in the DLL. Calls to these objects do not require cross-thread marshaling, making them more efficient.

2. Because an object is only accessed on the thread where it was created, calls are serialized so that a call is never interrupted by a call from another thread.

3. Arguments for cross-thread calls are marshaled, and the calling thread is blocked. This synchronization of data protects the calling thread?s state.

Dllhost.exe acts as a multithreaded host for COM+ components placed in application with Server Activation type. In case it has Library Activation type your client application directly acts as a host for COM+ components.

?and How the Theory Has Helped Us
As you can see apartment-model threading gets around the reentrancy problem.

1. Therefore you cannot see the ErrObject set in the first component from another component until the first component returns control to the caller. Hence just begin any method with On Error GoTo Hell statement and you will never see another?s error. Also propagate an error up to call stack of components and hope for our friends from Redmond.

2. The same thing is about App. Do you often use App properties in your COM+ applications to pass parameters between components? Sounds funny, does not it?

Conclusion
I have just shown a couple of interesting and unexpected things that I had seen behind simple and scanty line ?Do not use global variables in VB components under COM+?.

Special thanks to Tanya Rusakova for her Queen English (my native language is C++) and to Jimmy Nilsson for his infinite patience and kindness. The converse is also true.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist