[Previous] [Contents] [Next]

Apartments and Language Integration

While components written in C++ have the greatest flexibility and control over the apartment models in COM+, high-level programming languages such as Microsoft Visual Basic and Java have also been extended to support the evolving COM+ threading models. As you might expect, Visual Basic is currently the most limited environment in terms of threading models. Besides the legacy thread-oblivious model, Visual Basic provides support only for the COM+ multiple-STA model. The Microsoft Java VM, on the other hand, offers much better support for the apartment models. By default, components built in Java are marked ThreadingModel = Both in the registry, and the Microsoft Java VM automatically aggregates the FTM.

Threading Options for Visual Basic Components

Visual Basic was originally designed as a purely single-threaded environment. As threading has become pervasive in Windows, Visual Basic has been slow to adapt. Perhaps this is because Visual Basic has historically been so focused on creating GUI applications, so threading has seemed an unnecessary complexity. Now that Visual Basic has become a powerful component development tool, however, its limitations in the apartment department are more glaring. Recent versions of Visual Basic have allowed COM+ components to support either the legacy thread-oblivious model or the more modern STA model; currently, components built in Visual Basic cannot support the MTA or NA models. This means that serialization is a fact of life for COM+ components built in Visual Basic.

When multiple clients concurrently request the services of a single object, the requests are handled on a first-come, first-served basis. Recall that the STA model is synchronized with window messages, so method invocations are posted on the message queue and then processed sequentially. Actually, since objects implemented in Visual Basic run in the STA, serialization occurs at the apartment level. This means that if multiple objects are instantiated in one STA, a call to any one of the objects prevents all other objects in the apartment from executing concurrently.

One solution to this problem is to create an executable component and set its class Instancing property to SingleUse. This causes a new copy of the executable component to be launched for each new object instantiated. Since each object will run in a separate process, and therefore in a separate apartment, the objects can execute concurrently. Obviously, this is very inefficient use of system resources and does not scale well when a large number of objects are created. More typically, an in-process or executable component will set a class's Instancing11 property to MultiUse. This provides the standard behavior in which each instance of a coclass is created in the parent process and concurrent access of an object by multiple clients results in serialization. The possible values for the Instancing property are shown in the following table:

Instancing Property Value Description In-Process Components Executable Components
Private The class cannot be accessed by external clients. X X
PublicNotCreatable The class can be accessed by external clients, but only if first instantiated by the component. X X
SingleUse Every instance of the class launches a new process. X
GlobalSingleUse Like SingleUse, but the methods and properties of the class are global. X
MultiUse Multiple instances of the class are created in one process. X X
GlobalMultiUse Like MultiUse, but the methods and properties of the class are global. X X

Aside from the SingleUse option, the only way for a component written in Visual Basic to avoid the serialization imposed by the STA model is to make sure that each object runs in a separate STA. Since in-process components are not responsible for creating their own apartments, they have no control over the apartments in which their objects are instantiated. The best a Visual Basic_built in-process component can do is set its ThreadingModel value to Apartment and hope that the client will instantiate each object in a separate STA. Client applications written in Visual Basic instantiate all objects from one STA, but more sophisticated clients such as Microsoft Internet Explorer and Microsoft Internet Information Server have been optimized to run STA-based components in multiple STAs.

To specify the threading model used by an in-process component written in Visual Basic, choose Properties from the Project menu and then make your selection from the Project Properties dialog box, as shown in Figure 4-13. The default value is Apartment Threaded, and Visual Basic automatically sets the ThreadingModel registry value to Apartment for in-process components that support this model. Components that use the Single Threaded option are considered thread-oblivious, and therefore all objects exposed by such a component run in a single STA, regardless of how many STAs a client application might create. In-process components, especially ActiveX controls, that run in the legacy single-threaded mode do not work well with clients such as Internet Explorer or Visual Basic itself.

Another useful setting for in-process components created in Visual Basic is Unattended Execution. Selecting this check box tells Visual Basic that your component is a nonvisual component that should not display a user interface. Any statements such as MsgBox that attempt to display a UI are instead written to the Windows event log. This guarantees that the component will not freeze because of a modal dialog box that has been invoked by mistake.

Click to view at full size.

Figure 4-13. The General tab of the Project Properties dialog box, showing the Threading Model drop-down list for an ActiveX DLL project.

Executable components built in Visual Basic have slightly different options available in the Project Properties dialog box, as shown in Figure 4-14. If you select the Thread Per Object option, each new object is created on a new thread (a new STA). The problem with the one-thread-per-object approach is that the component might get overwhelmed by a lot of clients connecting simultaneously. Another threading option available to executable components, Thread Pool, limits the component to a fixed maximum number of threads. This sets the maximum number of STAs that can be created to satisfy client demand. A thread pool size of one puts the component in the legacy single-threaded mode; a larger thread pool means that the component can support multiple STAs. The objects instantiated on behalf of clients are allocated based on a round-robin algorithm. As clients request objects, Visual Basic creates each object on the next thread in the pool. After the specified maximum number of STAs have been created, Visual Basic simply starts over with the first thread. (The round-robin algorithm does not attempt any form of load balancing. For example, if one STA contains few objects while other STAs are more heavily loaded, Visual Basic does not attempt to assign new objects to the underutilized STA. Instead, the algorithm simply proceeds sequentially.)

Click to view at full size.

Figure 4-14. The General tab of the Project Properties dialog box, showing the Threading Model options for an ActiveX EXE project.

The multiple-STA-based threading model offered by Visual Basic is vulnerable to certain problems relating to global data in application. Since the multiple-STA model enables multiple threads to execute code concurrently inside a single component, any access by the objects in that component to global variables can lead to data corruption unless some form of processwide synchronization was used to limit such access. Since Visual Basic does not currently offer any thread synchronization primitives, Microsoft has not taken this approach. Instead, a copy of any global variables is made for each apartment. For example, say that a project contains the following declaration:

Public X As Integer

If two apartments are currently running in the executable component, two independent integers are named X—one for each STA, as shown in Figure 4-15. Needless to say, this considerably alters the semantics of global variables and can be confusing. Perhaps they should be known as apartment variables rather than global variables! To help initialize the global variables for each new apartment, Visual Basic calls the Sub Main procedure (if one exists) once for each new apartment. To find out what thread is executing the code, you can check the Win32 thread identifier for the current thread using the App.ThreadID property.

Click to view at full size.

Figure 4-15. Each apartment receives its own copy of global data.

Threading Options for Java Components

COM+ components built in Java are executed by the Microsoft Java VM (msjava.dll). In the registry, components built in Java have their ThreadingModel value set to Both. Thus, COM+ components built in Java implicitly support the STA and MTA models. In addition, the Microsoft Java VM automatically aggregates the FTM. In order to disable this functionality, you implement the com.ms.com.NoAutoMarshaling interface, an empty interface that simply acts as a flag to the VM.

Since most ordinary threads in a Java application do not have a message loop, by default the Microsoft Java VM does not instantiate STA model coclasses on the current thread. Instead, it instantiates STA-based classes on a special thread that it owns. This can be problematic because any Java-based calls to that object will result in a thread switch, which can hurt performance. By calling the com.ms.com.ComLib.declareMessagePumpThread method, an application can inform the Java VM that the thread will pump messages if necessary. The code fragment shown below calls declareMessagePumpThread and then enters a message loop using the GetMessage, TranslateMessage, and DispatchMessage method calls exposed by the com.ms.win32.User32 class. This technique allows an STA-based Java thread to make direct calls to an STA-based object instantiated on the same thread without being marshaled to the Java VM's STA.

private Object comObject = null;

public void run()
{
    com.ms.com.ComLib.declareMessagePumpThread();
    comObject = new component.InsideCOM();
    com.ms.win32.MSG msg = new com.ms.win32.MSG();
    while (com.ms.win32.User32.GetMessage(msg, 0, 0, 0))
    {
        com.ms.win32.User32.TranslateMessage(msg);
        com.ms.win32.User32.DispatchMessage(msg);
    }
}

You can use the com.ms.com.ComLib.threadStartMTA method to explicitly create a new thread running the MTA. This method calls CoInitializeEx with the COINIT_MULTITHREADED flag. In contrast, the standard java.lang.Thread.start method always calls the older CoInitialize function to create an STA-based thread.