Chapter 4. Threading and Apartment Models

The treacherous topic of COM threading models has been approached many times before, but I feel compelled to revisit this challenge because I have never read an explanation of COM threading models that satisfied me. You can have a very pleasant trip through this book, bypassing this section altogether. I think, however, that a basic understanding of threading, specifically, the COM+ apartment threading models, will help you understand how COM+ performs a lot of its magic.

Threads and Processes

Threads and processes are so simple, yet so misunderstood. To learn about them, let's first concentrate on a concept everyone understands—the process. The process is what your EXE lives in. Any memory your EXE uses, variables it declares, functions it calls, and so on exists inside a process that your operating system creates specifically for it. A process is like a playground surrounded by an invisible fence. Your application code can do what it likes in the playground specifically created for it and does not need to worry about the outside world. However, should your code try to escape the playground and overreach the fence, it will be terminated by the operating system. In fact, when you see a dialog box bearing the great big, red X that tells you the memory cannot be written or read, it usually means that your application tried to go beyond the fence and was terminated by the OS. In NT, the playground boundary is about 4GB, but it really has only 2GB (in Windows NT Enterprise Edition and, more recently, Windows 2000 Advanced Server, this is extended to 3GB) of memory to play with; the other 2GB is reserved by the OS.

A playground is only empty space. The existence of a playground implies that there is someone who will play in it. This someone is your application, and your application appears to the OS as a primary thread. At first, thread seems like a strange term to use, but it makes sense when you think about it. An application is a series of instructions threaded through time. It is fair, then, to refer to your application as a thread. Of course, your thread is not the only thread the OS is concerned with. There are many processes running, and thus, many threads executing at any given time on NT. Because most machines have only one processor, processor time must be distributed round robin to each thread in each process. To take the playground metaphor further, it is like all the threads in all the playgrounds are playing Red Light, Green Light with the OS who calls "Green Light! Now Red Light!" to each thread in turn. So every thread gets a short period of time to play in its playground, and then it must wait for its turn to come around again.

So far, we are assuming that every process (playground) has only one thread (player) in it. This can happen, but it is quite limiting because the process becomes myopically focused on one task and appears to the user as frozen until the task is complete. An application might want to do more than one thing at one time. Wall Street analytic systems, for example, have some calculations that can take hours to perform. You wouldn't like it very much if, after clicking the Calculate Yield Curve button, the user interface remained unresponsive for several hours. On the contrary, you expect to keep working with the application while it performs some task in the background.

Although there are some workaround (PeekMessage(), for example) methods of doing this (most of which can be found in Windows 3.1 cooperative multi-tasking), multiple threads are the answer. Let's say there are two kids (threads) doing different things but sharing all the toys (resources) in the same playground (process). When the CPU comes around to your application's playground, it calls Red Light! Green Light! to each of the two threads in your one process, giving each a limited time to play.

This brings up an important distinction—the OS gives time slices to threads, not processes. There simply can be no process without a primary thread, because it is the thread that is actually executing code; the process is just the place where a thread does its work. Thus, it is possible for a process to have more than one thread, that is, it becomes a multi-threaded process. Amulti-threaded process has the capability to perform many tasks with what appears to be the same time in single-CPU systems or is actually at the same time in the case of multiple-CPU systems. Our analytic system can now undergo a long calculation in one thread, but keep the user interface responsive to the user with another thread.

Race Conditions and Threading Issues

Many threads that require access to finite resources of a process must adhere to some form of thread synchronization—that is, multiple threads must share resources in an orderly fashion, one thread at a time. Basically, if threads are not kept in line, one thread might interrupt another at precisely the wrong time and take control of some resource that the first thread was not finished using. Threads are simply not aware of one another, so it is possible that one thread in a process might be modifying a file, for example, when the CPU suddenly suspends it (in OS terminology, "puts it to sleep" or "blocks" the thread) and activates the second thread. This newly running thread then, unaware of the first thread's modification, proceeds to read the file but gets inconsistent data because the first thread did not complete its write. You now have a race condition.

Writing multi-threaded code that protects against race conditions is not the easiest of tasks. The multi-threaded programmer has a host of tools available: mutexes, semaphores, critical sections, events, and others. An in-depth discussion of each primitive is outside the scope of the book, however, a brief introduction is not. The following code demonstrates how a process (which automatically contains one primary thread) can spin off a second, worker thread.

#include <process.h>

DWORD WINAPI PrintSomething100Times(char *lpszSomething)
{
         for(int i=0; i<100; i++)
            cout<<lpszSomething<<", i="<<i
                <<" and my thread id is "
                <<GetCurrentThreadId()<<endl;
}

void WorkerThreadFunc(LPVOID v)
{
         PrintSomething100Times("I am the worker thread");

}


main()
{
DWORD dwThreadId;

        CreateThread(0,0,WorkerThreadFunc,0,0,&dwThreadId);

        PrintSomething100Times ("I am the primary thread");


}

The output of the program in Listing 4.1 is something like:

Example 4.1.  A Simple Multi-Threaded Program

I am the worker thread, i=0 and my thread id is 2088
I am the worker thread, i=1 and my thread id is 2088
I am the worker thread, i=2 and my thread id is 2088
I am the worker thread, i=3 and my thread id is 2088
I am the primary thread, i=0 and my thread id is 806
I am the primary thread, i=1 and my thread id is 806
I am the worker thread, i=4 and my thread id is 2088
I am the worker thread, i=5 and my thread id is 2088
I am the worker thread, i=6 and my thread id is 2088
I am the primary thread, i=2 and my thread id is 806
I am the worker thread, i=7 and my thread id is 2088
I am the primary thread, i=3 and my thread id is 806
I am the primary thread, i=4 and my thread id is 806
I am the worker thread, i=8 and my thread id is 2088
I am the worker thread, i=9 and my thread id is 2088

If the intent of PrintSomething100Times() is to print something 100 times without interruption, we clearly have a problem. You will notice that the two threads are, in a sense, competing for PrintSomething100Times() such that one thread can't finish executing the function before it is usurped by the other thread. Suppose that this constitutes a race condition, and you wanted to make sure that one thread needs to be completely finished with the PrintSomething100Times() resource (that is, the thread should complete 100 contiguous prints statements without interruption) before the other thread gains access. You can use a Win32 critical section to get this behavior by protecting the PrintSomething100Times() resource. Simply add a critical section object and few method calls such that we have

CRITICAL_SECTION pCriticalSection;
DWORD WINAPI PrintSomething100Times(char *lpszSomething)
{
    EnterCriticalSection(&pCriticalSection);

    for(int i=0; i<100; i++)
       cout<<lpszSomething<<", i="<<I
           <<" and my thread id is"
           <<GetCurrentThreadId()<<endl;
    LeaveCriticalSection(&pCriticalSection);
}

main()
{
//other code ommitted for brevity
…
    InitializeCriticalSection(&pCriticalSection);

…
}

In Listing 4.1, only one thread may enter PrintSomething100Times() at a time because it is protected by a critical section.

Only one thread is allowed in a critical section at a time, so neither thread will be able to call PrintSomething100Times() as long as this function is executing in the context of the other thread. Thus the output of the improved application will now consist of one hundred strings printed by one thread followed by one hundred strings printed by the other thread with no intermixing.

There are other synchronization primitives aside from critical sections, but they all operate under the same premise: Prevent concurrent access to some resource from multiple threads that are running at the same time.

Note that while VB does not natively allow multiple threads, VB developers can take advantage of the Win32 API from VB to nonetheless create both threads and synchronization primitives. For example, one can declare an external reference to the Win32 function, CreateThread(), as follows:

include <process.h&>

DWORD WINAPI PrintSomething100Times(char *lpszSomething)
{
    for(int i=0; i<100; i++)
       cout<<lpszSomething<<", i="<<i
           <<" and my thread id is"
           <<GetCurrentThreadId()<<endl;
}

void WorkerThreadFunc(LPVOID v)
{
    PrintSomething100Times("I am the worker thread");
}


main()
{
    DWORD dwThreadId;

    CreateThread(0,0,WorkerThreadFunc,0,0,&dwThreadId);

    PrintSomething100Times ("I am the primary thread");

}

One can then take advantage of VB's AddressOf operator and can spin off a new thread. See Listing 4.2.

Example 4.2.  Creating a Thread Using the Win32 in VB

Sub NewThreadFunc(Dim NoArg as Long)
         'do something
End Sub

Sub NewThread_Click

Dim ThreadHandle as Long
Dim ThreadId as Long

  ThreadHandle = CreateThread(0, _
                            0, _
                            AddressOf NewThreadFunc, _
                            0, _
                            0, ThreadId)

End Sub

Aside from using Win32 function calls, there is no way to directly create threads in VB6 or earlier versions. These versions of VB simply do not support multi-threading, and VB IDE and debugger will not operate correctly if you execute the code shown in Listing 4.2. Some VB developers might disagree and point to the Project Properties dialog box (shown in Figure 4.1) which allows VB programmers to create a pool of threads.

VB's Project Properties Dialog

Figure 4.1. VB's Project Properties Dialog

Note that this option is greyed out and unavailable except in the case of out-of-process-(EXE) based servers. Because all COM+ components must be DLLs, this facility has no bearing on COM+. It is intended to provide load balancing for heavily used EXE-based automation servers by alternating new object creations on alternating threads in a pool or by creating a new thread to service each new object. Regardless, COM+ components are not allowed to spin threads and, as I said, they must be DLLs. Thus, this option is not relevant to COM+.

Apartments

Threads are useful and offer the developer a great deal of additional latitude. But with latitude always comes responsibility and complexity. Developers today want to leap right into the enterprise! They don't want to worry about writing thread-safe code when authoring objects that can, potentially, be used by multiple clients and threads at the same time. So, if the objects themselves are not going to be written to handle the rigors of multi-threaded clients, they must have some kind of guardian angel that protects them. They do, and the guardian angel is called, oddly enough, an apartment. The apartment provides COM+ with the "magic" ability to prevent race conditions from occurring; even though multiple threads/clients appear to access the same object at the same time.

It takes most developers time to grasp the concepts of COM threading and apartments. When you first hear that COM protects objects from concurrent access from multiple threads by means of an apartment model, you might then go hunting for apartments. If you know what a process is and how threads work, you might try to find some OS entity that corresponds to an apartment. But you won't find one, because apartments don't exist in any literal way. They are simply a concept, a term used to describe groupings of related objects and threads for the purpose of protecting COM objects from concurrent multi-thread access when the objects are not written to handle this. To be complete, I should mention that the role of apartments has evolved to accommodate COM+ contexts, which is discussed in Chapter 7, "Contexts."

For this chapter, however, we will focus on apartments in the context of their original intended purpose—protectionThe concept of an apartment makes sense only after we understand why protection is critical for a COM object. So, we will revisit apartments in the section "Enter the Apartment" later in the chapter after exploring some of the synchronization problems inherent in multi-threaded applications and how apartments help to resolve them.

Message Queues as Synchronization Aides

Languages like Visual Basic (up to version 6, at any rate) are single-threaded. Other languages like Java and C++ allow you to create threads. COM+, however, insists that any client, multi-threaded or not, should be able to safely use any server, multi-threaded or not. So, to make good on this promise, COM+ steps in and provides protection in the case of, for example, a multi-threaded C++ client application using a single-threaded VB server object. How does COM+ do this?

At the heart of every Windows'application, and potentially every thread, there is a message queue. There is also amessage loop whose purpose in life is to process messages from this queue. The code that processes a message loop—that is, retrieves the messages from the message queue and makes them available to the application for processing—is often referred to as amessage pump (see Listing 4.3).

Example 4.3.  A Simple Message Pump

while(GetMessage(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Every mouse click, mouse movement, minimize, maximize, DDE message. . .basically every event that can happen in the Windows operating system gets stuffed into a queue that this loop perpetually reads from. When an application's UI becomes unresponsive or freezes, it is because this bit of code is hung.

In fact, if you strip away all the abstractions provided by most modern development environments and return the core Win32 API calls, the simplest possible Windows application is very simple. At its most basic, it creates a window, associates a windows procedure with that window to receive all the events (menu selections, windows resizing/movement, repaint, mouse clicks, and so on), runs a message pump to read these events from the queue, and processes them one at a time through the windows procedure (see Listing 4.4).

Example 4.4.  A Win32 Message Pump and Window Procedure

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   int wmId, wmEvent;

   switch (message) {

      case WM_COMMAND:
         wmId    = LOWORD(wParam);
         wmEvent = HIWORD(wParam);
         //Parse the menu selections:
         switch (wmId) {

            case IDM_ABOUT:
               DialogBox(hInst, "AboutBox", hWnd, (DLGPROC)About);
               break;

            case IDM_EXIT:
               DestroyWindow (hWnd);
               break;

            // Here are all the other possible menu options,
            // all of these are currently disabled:
            case IDM_NEW:
            case IDM_OPEN:
            case IDM_SAVE:
            case IDM_SAVEAS:
            case IDM_UNDO:
            case IDM_CUT:
            case IDM_COPY:
            case IDM_PASTE:

            default:
               return (DefWindowProc(hWnd, message, wParam, lParam));
         }
         break;

      case WM_NCRBUTTONUP: // RightClick on windows non-client area...
    <do the appropriate thing>
            break;

        case WM_RBUTTONDOWN: // RightClick in windows client area...
    <do the appropriate thing to respond to a right click>
            break;

      case WM_DISPLAYCHANGE: // Only comes through on plug'n'play systems
      {
      break;

      case WM_PAINT:
       <repaint the window>
         break;

      case WM_DESTROY:
         PostQuitMessage(0);
         break;

      default:
         return (DefWindowProc(hWnd, message, wParam, lParam));
   }
   return (0);
}

//Creation of window and a message loop

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
  MSG msg;
  WNDCLASS  wc;

// the main window.
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = (WNDPROC)WndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = hInstance;
        wc.hIcon         = LoadIcon (hInstance, szAppName);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

RegisterClass(&wc);

HWND hWnd;

hWnd = CreateWindow(szAppName, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
      NULL, NULL, hInstance, NULL);


  // Main message loop:
   while (GetMessage(&msg, NULL, 0, 0)) {

         TranslateMessage(&msg);
         DispatchMessage(&msg);

   }

   return (msg.wParam);

}

Basically, as the message pump gets each message from the window's message queue, it messages it briefly with TranslateMessage() and then calls DispatchMessage(), which calls WndProc(). WndProc() was associated with the window before the window was created and contains a large switch/case statement. Every event that can happen to the window must be either handled by the developer in the switch/case block or delegated to the default handler via DefWindowProc().

You might be wondering what a window's message queue has to do with protecting COM objects from concurrent access, but think about itQueue means an orderly, one-at-a-time delivery. The architects of Windows chose to employ queues to tame the huge number of messages that can arrive at any time. Without a queue, a Windows application would become swamped with the sheer volume of messages; but with a queue, it gets orderly, synchronized delivery. And message queues are a built-in service of the operating system that any thread can create and use.

So, can message queues somehow be used to synchronize method calls made by different threads? Yes. And this is exactly what COM does to protect single-threaded objects. It turns all method calls made by clients into messages and stuffs them into the message queue. Prior incarnations of COM actually directed these messages into the message queue of a special, hidden window (COM+ still performs the same trick, but uses a new type of window, the message-only window, discussed later in the section, "Serialization Through Message Queues"). The server-side COM object then read from the queue, one method at a time (see Figure 4.2).

Threads and Objects

Remember that processes (EXEs) are nothing without their primary thread. It is the primary thread that performs tasks; the process is the place where it does the work. If a client application can be said to create a COM object, you are really saying that the primary thread of a client EXE process created a COM object. In a distributed case, a proxy of the object can be brought into the process for the thread to act on, although the object itself is created and remains on the server machine. When the creating thread calls a method on an interface of the proxy, that call gets remoted through the RPC channel to the implementation of the object on the server—so far, so good.

A Windows message queue being used to serialize method calls to a single-threaded object.message queuessynchronization aides asqueuesmessagesynchronization aides assynchronization aidesmessage queues asaidessynchronizationmessage queues as

Figure 4.2. A Windows message queue being used to serialize method calls to a single-threaded object.

But what happens if the primary thread is not the only thread in the process? Let's suppose, for example, there are two threads in the process. Thread 1 creates the object and wants to give the object's interface it is holding to thread 2. If your object is not thread-safe, it is in trouble because now there are two threads that can call its methods at any time. For the purpose of example, assume this COM object books seats on planes as part of an airline reservation system. Now, thread 1 can call ReserveSeat() one millisecond before Thread 2 calls ReserveSeat(). If there is only one seat left on the plane, Thread 1 might reserve the seat but might not finish changing the seat status to Reserved before Thread 2 barrels in and, seeing the erroneous Open status, also reserves the now unavailable seat. COM protects against this by serializing all method calls through a message queue. In other words, COM redirects all concurrent method calls to a single-lane, one at a time queue.

This solves the problem of concurrency, but isn't there an efficiency cost? Yes. A COM object protected in this way does not scale well to meet the demands of a many-threaded client or multiple clients. As you'll see, however, COM+ provides an infrastructure where this scenario tends not to occur, and so the limitations of serialized messages are not that limiting.

Serialization Through Message Queues

You might be wondering what exact form this serialization takes. The answer is surprising. Every thread in the operating system is allowed to have its own queue. In fact, message queues are the only way threads can talk to one another. Threads can share variables and other resources, but if Thread 1 wants to say something to Thread 2, 1 can sa message to 2 if 2 has created and is servicing a message queue (see Figure 4.3).

In "traditional" COM as implemented in NT 4, Windows 95, and 98, recall that a hidden window with a message queue was used to serialize method invocations on a COM object. In fact, on these operating systems you can actually "see" this window using a platform SDK utility calledSpy++. In COM+, however, this hidden window can no longer be found using Spy++, but that doesn't mean it isn't there. Windows 2000 introduces a special kind of window called a message-only window. Basically, it has the primary mechanisms of a traditional window—a valid system handle (HWND) and message queue, but lacks any kind of UI component. You might wonder what the point of such a window is. There are times when a mechanism intended for one purpose becomes useful for another. Without getting into too much detail, the use of a message-only window provides the simplest mechanism for COM+ to provide serialized method delivery for COM objects that need it. These windows can be "seen" programmatically by executing the code in the following sidebar "Finding Message-Only Windows."

At any rate, the hidden, message-only window is the protection, and it is erected by COM whenever protection is required. But how does COM know when this protection is required? Returning to the earlier example, how does COM even know that Thread 1 gives the interface of an object it creates to Thread 2? And what does any of this have to do with apartments?

Thread 1 creates a second thread and sends a message to it.

Figure 4.3. Thread 1 creates a second thread and sends a message to it.

Finding Message-Only Windows

Finding message-only windows is not difficult; one needs only to use a few Win32 function calls that are easily accessible from Visual Basic. A VB application demonstrating the process follows.

 'Declare the Win32 APIs we need:
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long

'Win32 API Constant used with FindWindowEx to indicate we only
'want to search for message-only windows:
Const HWND_MESSAGE = -3

Dim WindowHandle As Long
Dim ClassName As String * 100
Dim WindowText As String * 100
Dim ThreadId As Long
Dim ProcessId As Long
'Iterate through all the message-only windows on the system.
'When FindWindowEx has enumerated through all of them, it will
'return a window HANDLE of 0.

Do

    WindowHandle = FindWindowEx(HWND_MESSAGE, WindowHandle, vbNullString, vbNullString)
    ClassName = "": WindowText = ""
    GetClassName WindowHandle, ClassName, 100
    GetWindowText WindowHandle, WindowText, 100
    ThreadId = GetWindowThreadProcessId(WindowHandle, ProcessId)

    'Output the information to the debug log:
    Debug.Print WindowHandle, Trim(ClassName), Trim(WindowText), ThreadId, ProcessId

Loop Until WindowHandle = 0

Enter The Apartment

You might expect that COM simply creates a message-processing thread whose job is to receive these redirected messages (method calls converted by COM to Windows-style messages) and distribute them, one at a time, serially to the COM object. You would almost be right.

Prior to COM+, COM intercepted the method calls from RPC, turned them into Windows messages, and redirected them to a hidden window of type OleMainThreadWndClass. This window was an ordinary, if invisible, window, and you could verify its existence with Spy++. The message queue for that window was used in the fashion just described to serialize the method calls (turned into messages by COM). After the messages were posted to the window's queue, they could be fed one-at-a-time to the server object that could not otherwise handle concurrent access by multiple method calls from multiple threads.

Finding a good real-world analogy for apartments and message queues is difficult, so I have to draw on a little bit of personal history to explain them. When I went to summer camp, all campers were given swim tests and put in one of two categories—Bluefish or Whale. As stronger swimmers, whales were allowed to swim together in the deep of the swim area with minimal supervision. Bluefish, on the other hand, were considered weaker swimmers. They could only swim in the shallow, heavily supervised end, and if that wasn't enough, each Bluefish had to stay in his own little 4 square-foot area. When it was time for swimming, we all had to line up on the dock and declare our affiliation—Whale or Bluefish. Whales walked to the far of the dock where they could swim together, mostly unsupervised, and Bluefish were stuck in the shallows in their little 4 × 4 areas. This is a sad, but true story. This is all COM apartments are—Whale and Bluefish affiliations. In COM, the Multi-Threaded Apartment (MTA) is the Whale and the Single-Threaded Apartment (STA) is the Bluefish. Threads and objects in the MTA are left alone to sink or swim together (they are strong swimmers), but threads and objects in the STA are considered weaker swimmers that need protection.

A thread must declare its apartment affiliation before it can do anything with COM or a COM object. So, the thread goes to the dock and has to declare Whale or Bluefish (MTA or STA). In Visual Basic 6 and earlier (the final features for VB7 have not been finalized at the time of this writing, but Microsoft has announced that VB7 applications will be able to spin threads), where an application can only have one thread, it declares itself implicitly as belonging to the STA (Bluefish). In C++, however, the declaration is explicit and clear cut. Every thread in a COM C++ application must declare its affiliation. It does so with the following statement in Listing 4.5.

Example 4.5.  Declaring a Thread to be MTA or STA in C++

main()
{
    CoInitializeEx(0, COINIT_MULTITHREADED); //MTA/Whale
  //  Or
    CoInitializeEx(0, COINIT_APARTMENTTHREADED); //STA/Bluefish

}

Threads always establish their affiliation from the outset. You might wonder why a thread would ever say it is in the STA when the MTA seems like a lot more fun. The answer has to do with the COM object the thread creates. If the object can handle concurrent access by multiple threads and the client is written in C++, absolutely, go MTA all the way and initialize your thread to be COINIT_MULTITHREADED. Unfortunately, however, Visual Basic client applications and VB-generated COM objects can only live in an STA This means that the C++ client thread that instantiates and uses a VB COM object should be STA too.

Conversely, if the client application is written in VB, the server object gains nothing by swimming alone in the MTA because the VB application is putting around in the shallows of the STA. This is not to say that differing apartments can't mix; they absolutely can. But imagine a Whale trying to play catch with a Bluefish. The swimming areas we had at camp were divided by a wood decking, so if a Whale wanted to throw a ball to a Bluefish, the lifeguard probably had to step between the two and pass the ball from one to the other. That's a waste of resources and not much fun.

Single-Threaded Apartments (STA)

When a client thread is created and calls CoInitializeEx(), it declares whether it is in an STA or the MTA. If it chooses the STA, a new STA apartment is created for it. A process might have many STAs in it, one for every COM thread (if every thread so chooses). Note that exactly one thread lives in one STA. Using the Bluefish metaphor,

every thread is like a swimmer in his own 4 × 4 area. If a thread in an STA creates a COM object and the object declares itself STA friendly (we will talk about how a COM DLL can declare its thread affinity in the section "Declaring Apartments"), that object can be thought of as moving into the STA with the thread.

Because an STA has, at the very most, one thread in it, there is no way that the poor, single-threaded object living in an STA can get walloped by multiple method calls from multiple threads. No matter how you look at it, there is no way for an object to get hurt.

Let's look at the following two scenarios:

  • Scenario 1—STA Client Thread Creating an STA Object on the Same Machine (Library Application). In COM+, it is possible for a COM object to be brought into the process space of the client who requests it if the client and component are on the same machine. Such an application is called alibrary application. If the client thread is STA and the object is STA, the client thread's STA apartment has just one thread and one object. An object can only be called on by the thread or threads in the apartment in which the object resides. So in this case, you're safe—there is only one thread in the apartment.

  • Scenario 2—STA Client Thread on One Machine and STA Object on Another (Server Application). In the previous scenario, you imagine that the thread and object live in the same STA. This is still true in this scenario, true in a philosophical if not a literal sense. Even though, technically, the client is in one STA and the server object is in another STA (possibly on a different machine), you can think of the client thread and server object as being in the same conceptual apartment. In COM+, most often, you have a client thread in an EXE on one machine that creates and calls methods on an object that resides in a server thread on another machine. In this case, you really have two STAs—one that the client thread and the object's proxy live in and another STA where the server object lives with a single thread provided by the COM+ surrogate EXE. See Figure 4.4 for a simplified representation (serializing hidden windows omitted).

At the of the day, however, the object is still safe. Take each thread in the two STAs and imagine that they blur into one logical thread that sweeps from the client EXE into the surrogate where it calls into the object. When you look at it this way, there is only one thread that can call into the object.

Simplified view of a method call being made from one STA to another over the network.

Figure 4.4. Simplified view of a method call being made from one STA to another over the network.

Multi-Threaded Apartments (MTA)

Although every thread that enters an STA always ends up as the sole tenant of its own apartment, an MTA is a big apartment where many different threads can move in together. This means a process can have many STAs but only one MTA. A thread-safe server object can live in an MTA because it is prepared to be hit by any number of threads living in the same apartment. Graphically, you have what is shown in Figure 4.5.

MTA is the easiest scenario to envision if you understand how an RPC server works. In the simplest case, an RPC server is an EXE that is running and listening for remote clients to connect to it and call methods on it. Remember that the client application has proxy functions that remote the call to this waiting, listening server where the function call executes.

The server might have many clients running that can call methods on it at any time, so all code in the server must be thread-safeThe fact is when a client makes an RPC call, this call is carried into the server on an arbitrary thread from the RPC server's thread pool. A thread pool is simply a collection of living threads that are waiting for a method call request to come in. When a method call does come in over the wire, one of the threads in the pool is dispatched to handle it. When the call is complete, the thread goes back into the pool. Although the server could create a new thread for every call, the server avoids the expensive overhead of creating a new thread on demand by creating them ahead of time in a pool. Of course, if more method calls come in than there are threads in the pool to handle them, new threads are created (although their lifetimes might not extfar beyond the completion of the call).

A process can have multiple STAs but only one MTA.

Figure 4.5. A process can have multiple STAs but only one MTA.

MTA is basically the RPC client/server scenario—method calls come into an object living in an MTA on some random thread, at any time and possibly at the same time. Objects that want to live in an MTA must be thread-safe. The simplest possible scenario to envision is one where a multi-threaded C++ client application accesses a thread-safe COM object. In this situation, every thread in the client calls CoInitializeEx(0, COINIT_MULTITHREADED), thus entering the MTA. If the server object declares itself as MTA friendly (again, we will discuss how to do this in the section "Declaring Apartments"), you have a fairly ordinary RPC client/server configuration. COM does not step in and give any form of protection to the object.

Protecting the STA with Message Queues

COM determines what objects need protection by evaluating the following:

  • Where each new thread declares it wants to be—STA or MTA

  • What each object declares its capabilities to be—STA, MTA, or either

  • What thread creates what object

If COM sees that an STA client thread creates an STA-compatible object, there is really no danger of race conditions. If the client and server object share the same process as in the case of a COM+ library application, direct vtable pointers are used, and COM does not insert any kind of protection. In most cases, however, COM still creates the hidden, messaging windows. However, COM does not need to do a thing if you have an MTA client thread that creates an MTA-compatible object. In this MTA-MTA case, the author of the object is responsible for making sure that the object is thread-safe or re-entrant. COM only needs to step in when you have mixed models—an STA client thread to MTA object, or vice versa.

Practically speaking, this doesn't happen as much as you might think. There is a heavy performance hit incurred when mixing models, so developers try to keep things the same. Visual Basic can only produce STA clients and server objects, and most commercial COM clients, particularly those having a GUI and/or acting as host for ActiveX controls, such as Internet Explorer, tto rely on the STA. In my experience, you are likely to find mixed models in the following cases:

  • Scenario 1—Inexperienced C++ Client-Side Developers Writing Code. Inexperienced C++ client application developers often take the path of least resistance. If they have a UI, STA is easiest; they will already have a message pump running for their window, which picks up COM method messages as well. If they do not have a UI and are not especially comfortable with message queues (or just don't want to be bothered to write extra code), inexperienced C++ developers opt for the simplicity of the MTA. MTAs do not require message processing. This results in faster development of the client, but drastically reduced performance when the MTA client thread instantiates and uses an STA object.

  • Scenario 2—High Performance Out-of-Process (EXE based) COM Singleton Server Used by C++, Visual Basic, and Visual J++ Clients. The concept of thesingleton server—that is, one big EXE server supporting multiple clients—is completely out of vogue these days. COM+ demands that your servers are DLLs and that your objects try not to rely on keeping internal state for extended periods of time. These two factors make this type of mixed model less likely and is found mostly in legacy scenarios.

  • Scenario 3—High-Performance Infrastructure Objects Used by VB Clients. Free-threaded COM objects are written in C++ with the explicit purpose of servicing multi-threaded clients in some form of high-stress infrastructure capacity. These objects are low-level system objects and are not intended to be used by UI clients. Then, someone comes along and, for whatever reason, decides to write a Visual Basic client for one of these objects.

As you can see, only the C++ developer has the latitude to play with the MTA. Due to the widespread use of STA-only Visual Basic, you are unlikely to find many MTA-only client and servers on the market.

At any rate, it is difficult to explain all possible interactions between clients and servers with different threading models, so I will not get into the depths of it right now. For now, here is what I want you to remember:

  • The server threads of STA-inhabiting objects and STA client threads always have running message loops. Remember that all remote method invocations made on STA objects are transformed from RPC calls into windows messages and redirected to a hidden window (OleThreadWndClassName). In languages like VB, the message loop is automatically written into your code. Message loops are optional in J++, and although you can run your own in an ugly fashion, a default STA is created by the J++'s VM (Virtual Machine). In C++, however, you must write this loop yourself. The nice things about STAs are that all method calls to an STA object are serialized through a hidden window. The result: Race conditions never occur, so the object does not need to be thread-safe.

  • MTA client threads and server threads of MTA-compatible objects do not have message loops. There are no hidden windows or COM tricks. The internals of this process are straightforward RPC, and there is very little COM-inspired complexity. Clients sRPC methods from any thread that calls CoInitializeEx(0, COINIT_MULTITHREADED), and the server receives the method calls from an arbitrary thread in its RPC pool, which carries the call into the server and into the object. MTA objects can get hit by multiple requests at the same time, so they must be written to be thread-safe.

  • If you mix models, COM needs to step in and provide protection. This inevitably affects performance, so it is best not to mix your models if you can avoid it.

  • If you develop your clients and server objects solely in Visual Basic, you never need to worry. Visual Basic 6 and earlier can only produce STA clients and objects, although VB7 will probably allow for a new type of apartment known as the Thread Neutral Apartment, or TNA (discussed later in this chapter).

Marshaling Interfaces

I begin this section by stating an absolute, immutable mandate of COM. And although only C++ programmers have the option to violate the following rule, it is important for developers of all languages to understand it:

  • You can never, ever,pass an interface pointer directly from one thread to another.

First of all, why would you want to do this? You might have a situation where Thread 1 creates a COM object and obtains an interface, but wants to give this interface to Thread 2. In this way, Thread 2 could call a method that might take awhile to execute, leaving Thread 1 free to go about its business. Visual Basic users never have this scenario because a VB application has, at most, one thread of execution. Only J++ and C++ developers have the option to share interfaces between threads. J++ developers, however, can rely on the VM to automatically take care of all the messy details of moving an interface between threads. As always, it is the C++ developer who bears the responsibility of total freedom and must be disciplined in his practices and familiar with all the gory details. So J++ and VB readers, if you're interested, read on; if not, skip ahead.

Let's revisit our previous scenario to see if we can spot the problem. Thread 1 creates a COM object, obtains an interface, and then gives that interface to Thread 2. Remember that in C++, an interface is simply a pointer to a vtable, so in this scenario, you pass a pointer from one thread to the other. Let us further assume that the COM object is apartment-threaded, meaning it can only exist in an STA because it needs protection from concurrent access. Do you see the difficulty? Both threads have an interface pointer to the object, so both threads can call into the object at the same time. The object simply cannot handle this, so you have a problem. But can't COM take care of this by converting all method calls from both threads into messages via a hidden window and then feeding them to the object one at a time? Yes, it can. But to perform this synchronization magic, COM must first know what thread created what object and what threads have which interfaces. In short, you must inform COM that you are sharing an interface between two threads so that COM can decide what needs to be done to provide protection.

In plain C++, passing an interface pointer from one thread to another does not, in any way, involve COM. You are simply passing a variable (that happens to have an address of a vtable) from one thread to another. As far as C++ is concerned, the variable can have anything in it. At the end of the day, it is just assembly code that is manipulating bytes in an address, and there is no way for COM to know what you're up to. Imagine COM's surprise then when Thread 2 comes out of nowhere and starts firing methods at the poor object.

You must tell COM that you want to share an interface between threads, and predictably, there is an API call you must make— CoMarshalInterThreadInterfaceInStream(). The name of this API is not exactly intuitive, but its purpose is straightforward. Just as you would want to send a delicate item across the country in a sturdily boxed, bubble-wrapped package complete with the sender's address, COM wants to send interfaces between threads in a special kind of package. The package in this case is astream, which is nothing more than an array of bytes. CoMarshalInterThreadInterfaceInStream(), then, simply packages up the interface into an array of bytes (Stream) for transport. You might be interested to know what the stream contains, but for now just think of it as holding all the housekeeping details, particularly the sender's apartment address.

After you have called CoMarshalInterThreadInterfaceInStream() from the thread that first obtained the interface, you are left with an IStream pointer (see Listing 4.6).

Example 4.6.  Marshaling an Interface Into a Stream

//Thread 1
IStream *pIStream;

hr = CoMarshalInterThreadInterfaceInStream(
         IID_ICalc,
         pICalc,
         &pIStream);

This pointer can be sent between threads. If this IStream pointer is given to Thread 2, Thread 2 can then call CoGetInterfaceAndReleaseStream() to turn the stream back into the original interface which Thread 2 can now use (see Listing 4.7).

Example 4.7.  Retrieving a Marshaled Interface From a Stream

//Thread 2
ICalc *pICalc;
hr = CoGetInterfaceAndReleaseStream(
        pIStream,
        IID_ICalc,
        (void **)&pICalc);

Actually, Thread 2 might up with a proxy, a direct pointer, a proxy to a proxy. . . . COM takes care of the housekeeping details and gives Thread 2 whatever illusion is necessary to make it think it has a direct interface to the object while protecting the object from any possibility of concurrent access. By calling CoGetInterfaceAndReleaseStream() and CoMarshalInterThreadInterfaceInStream(), you are giving COM the opportunity to do the right thing. Determining the right thing to do, so as to provide protection for all scenarios, gets you into a hornet's nest of combinatorial possibilities dependant on threading models, process contexts, and so on. If you follow the rules of marshaling interfaces, you can remain blissfully unaware of the implementation details. A thread's apartment affiliation is one of the housekeeping details stuffed into the stream as it informs COM what protection is necessary.

Listing 4.8 is a code snippet demonstrating how to pass an interface pointer between two STA threads in C++.

Example 4.8.  Demonstration of Marshaling and Unmarshaling an Interface Pointer Between Two Different Threads

//Source in: book/code/c++/ThreadPassInterface/ThreadPassInterface.cpp


//Stream can be global, available to all threads in the process
IStream* g_pIStream;        // The stream we will be passing


//Thread 1, packaging interface for delivery to another thread
...
    hr = CoMarshalInterThreadInterfaceInStream(
                 IID_ICalc,
                 pICalc,
                 &g_pIStream);
...
//Thread 2, unpackaging the interface
ICalc *pICalc;

    hr = CoGetInterfaceAndReleaseStream (
                 g_pIStream,
                 IID_ICALC,
                 (void**)&pICalc);

InJ++, this code is unnecessary because marshaling occurs automatically. Specifically, COM classes are represented as Java Callable Wrappers (JCW), which contain additional attributes that inform J++ when and how to marshal interfaces between threads.

Similarly, there is nothing to do in VB because VB client applications and servers can have only one thread. In situations where ActixeX classes (interfaces) are used as method arguments to pass an ActiveX class reference between processes or machines, the marshaling is done automatically.

Global Interface Table (GIT) Marshaling

The marshaling method discussed in the previous section is fine, but has a couple of limitations. One limitation is that after an interface is marshaled (packaged), it can only be unmarshaled (unpackaged) once. (Actually, CoMarshalInterface() can be called with theTABLE_STRONG attribute. But even this can encounter problems if the interface being marshaled is a proxy.) In other words, if I call CoMarshalInterThreadInterfaceInStream(), sthe stream to another thread, and call CoGetInterfaceAndReleaseStream(), the interface is obtained, but the stream is, for lack of a better word, spent.

The one-unmarshal-per-marshal is not that big of a deal, but it can be limiting in certain circumstances. For example, imagine that Thread 1 obtains an interface pointer for some kind of event notification object. This notification object might be useful for all the threads of the client process that want to send notifications. It is nice if Thread 1 can post this useful interface in some globally accessible area, and all other threads simply get this interface when they need it. You might imagine that Thread 1 can simply call CoMarshalInterThreadInterfaceInStream() and put the resulting stream pointer in a global variable where any thread can unmarshal it via CoGetInterfaceAndReleaseStream(). But this call only succeeds for the first thread and fails for all subsequent attempts by other threads.

I have seen a number of different workarounds for this problem. However, Microsoft introduced something called the Global Interface Table (GIT) in NT 4.0 Service Pack 3. The GIT's purpose is to solve this problem and allow a global space where popular interfaces can be placed and then be unmarshaled by different threads in different apartments again and again.

The GIT is a system object that you create and obtain an interface for (see Listing 4.9).

Example 4.9.  Obtaining an Interface to the GIT

IGlobalInterfaceTable *pIGIT;
// Create an instance of IGlobalInterfaceTable:

HRESULT hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IGlobalInterfaceTable,
                (void**)&pIGIT);

You can then add the interface you want to make available as shown in the following:

DWORD m_cookie;
pIGIT->RegisterInterfaceInGlobal(pICalc,
                IID_ICalc,
                &m_cookie);

And now, any thread can obtain and use this interface with the following code:

pIGIT->GetInterfaceFromGlobal(m_cookie,
    IID_ICalc,
    (void **)&pICalc);

A complete example of a multi-threaded client that uses the GIT to pass an interface between threads follows in Listing 4.10.

Example 4.10.  A Multi-Threaded Client Marshaling an Interface Between Two Threads Using the GIT.

//full GIT source in book/code/c++/GIT
DWORD g_cookie;

DWORD WINAPI NewThreadFunc(LPVOID );

main()
{
 DWORD dwThreadId;
 HRESULT hr;

 CoInitializeEx(0, COINIT_APARTMENTTHREADED);

 ICalc * pICalc;
 IGlobalInterfaceTable *pIGIT;

 //Below, create a new instance of the CalcSDK object and obtain an ICalc interface.
 hr= CoCreateInstance(CLSID_CalcSDK, 0, CLSCTX_ALL, IID_ICalc, (void**)&pICalc);

 hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IGlobalInterfaceTable,
                (void**)&pIGIT);

 pIGIT->RegisterInterfaceInGlobal(pICalc,
     IID_ICalc,
     &g_cookie);

 pIGIT->Release();

 CreateThread(0,0,NewThreadFunc,0,0,&dwThreadId); //spin off a new thread

 CoUninitialize();

}

DWORD WINAPI NewThreadFunc(LPVOID )
{
 ICalc * pICalc;
 IGlobalInterfaceTable *pIGIT;

 hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IGlobalInterfaceTable,
                (void**)&pIGIT);

  pIGIT->GetInterfaceFromGlobal(g_cookie,
   IID_ICalc,
   (void **)&pICalc);

  cout<<pICalc->Add(2,3)<<endl; //call a method of ICalc

  pICalc->Relase();
  pIGIT->Release();

}

Moving Interfaces Between Processes

If you want to move an interface pointer obtained on one thread to another thread, you must marshal it. This is true if you are moving the interface between two threads in two separate apartments (as shown in the preceding section) and also true if you are moving the interface between two threads in the same apartment as is the case in an MTA.

To a large extent, COM does not distinguish between different threads in one process and threads in different processes. Interface pointers must also be marshaled if they are moved between processes on the same or different machines. Fortunately, you don't need to write the code to do this. Because interfaces are defined in Interface Definition Language (IDL), they can be used as arguments to methods in IDL. It is possible, then, to have a method of an interface that takes another interface pointer as an argument. In fact, this is how a client can create and pass an interface of the one object to another object. The IDL shown in Listing 4.11 allows the holder of the IAlarm interface to pass in an IWakeup interface pointer to the remote implementation object that supports IAlarm.

Example 4.11.  IWakeUp and IAlarm IDL IDL Interface Declarations

//tags omitted for brevity
//this interface would be implemented by some object who's implementation resided on the client thread.

    interface IWakeup : IDispatch
    {
        HRESULT WakeupProcedure();
    } ;

//tags omitted for brevity
//this interface would be implmented by a remote object

    interface IAlarm : IDispatch
    {
        HRESULT SetAlarm([in] IWakeup* Wakeup, [in] long seconds);


    } ;

In this scenario, the IWakeup interface belongs to an object that was declared and created in the client thread (a local object) and given to some remote object that supports IAlarm—let's call it AlarmClock. In this way, AlarmClock can call back into the object living in the client thread and ask it to run WakeupProcedure(). Basically, you have built an event notification mechanism. The client has an interface pointer to an object residing in a server DLL, but the server object has an interface pointer residing in a client thread.

In this case, you do not need to call CoMarshalInterthreadInterfaceInSteam(), even though, technically, you are passing an interface pointer to different threads (albeit, they are different machines). COM does this for you. Because you are invoking an IDL-defined interface and using COM's underlying RPC, COM is directly involved in helping you pass the interface and can call the appropriate marshaling code on your behalf.

In Visual Basic, when you pass an ActiveX class (in reality, ActiveX classes are just interfaces) as an argument to a function or subroutine, the interface is being marshaled. Remember that Visual Basic ultimately uses type libraries and RPC, just like C++ and J++.

Marshaling Interfaces the Hard Way

On a more esoteric note, it is possible to marshal an interface between different processes on different machines without even involving the network. Packing up an interface into a stream is serious business—the stream contains a great deal of bookkeeping information including IP address of the originating object, apartment affiliation, and much more. A stream is an entirely self-contained, self-describing binary package. Nothing is needed by the destination process to rehydrate the stream and obtain a live interface. The stream contains all the information needed to re-establish a connection to the host object. A stream, then, can be passed from one machine to the other via floppy disk, email attachment, or zip disk. As long as the binary structure of the stream is preserved, all the destination process needs to do is read the stream from disk into an in-memory stream, unmarshal via the CoUnmarshalInterface() function, and voila!—a live, connected interface is obtained.

Floppy disks are not, obviously, an efficient way to move interfaces about. FedEx and UPS are expensive to incorporate in a distributed architecture. Plus, a stream thus packaged and transported had better reach its destination in about 5–6 minutes, at which time COM will invalidate it.

Declaring Apartments

Up until now, I have said that in-process objects can specify what types of apartment they can live in. Now, I will tell you how. In-process objects have a Registry entry called ThreadingModel, which can be one of the following four values:

  • Apartment.Object is not thread-safe and must live in an STA.

  • Free.Object is thread-safe and insists on living in an MTA. Such an object will be serviced from a dedicated pool of threads. Any calls made to an MTA object from an outside STA will involve a performance-impacting thread switch, as the STA thread must hand off execution to one of the pooled MTA threads (this thread-switch happens in spite of the fact that all threads are in the same process and the object, being thread-safe, wouldn't be in danger.) However, if the client thread making the call happens to originate in the MTA or enters the MTA via a call to CoInitializeEx(0, COINIT_MULTITHREADED), that thread will make the call directly into the object without requiring a thread-switch to a pool thread.

  • Both.Object doesn't care; it can live in an STA, MTA, or TNA (Thread Neutral Apartment). The value Both comes from the days when the choice was between Apartment and Free. An object so marked must be thread-safe (or have synchronization services configured), but it will enjoy improved performance because it will dwell in the apartment of its creating thread regardless of whether the apartment is STA or MTA. Thus, threads in the host's apartment will be able to execute methods of the object directly without a thread switch. However, thread switches will still occur if an interface to an object marked "both" is passed to another apartment.

  • Neutral.This is the preferred setting as of Windows 2000. Objects so marked will live in a new apartment known as the TNA. We will discuss this new addition in the upcoming section "The Thread Neutral Apartment (TNA)."

In the Registry, ThreadingModel appears as shown in Figure 4.6.

Sometimes there is some confusion as to why this Registry entry is necessary. Doesn't every thread explicitly state its threading model by calling CoInitialize() with COINIT_APARTMENTTHREADED or COINIT_MULTITHREADED? Yes, but remember that DLLs are passive blocks of code. They require a process to load them and initiate any form of action. So, although the client threads of the process that will load and use the DLL will declare their threading model, how is COM to know whether the COM objects that the threads will create require protection? Simply put, COM reads the Registry. If client thread T1 declares that it is free-threaded (MTA) and it creates an object that is marked in the Registry as Apartment, COM knows that there's a potential problem and performs the appropriate actions.

Threading model for a component as declared in the Registry.

Figure 4.6. Threading model for a component as declared in the Registry.

The Free-Threaded Marshaler (FTM)

Simply put, the free-threaded marshaler lets you violate the rules of COM threading. Normally, when a thread from oneapartment in a process invokes the method of an object residing in another apartment a thread switch occurs, the thread that makes the invocation is not the thread that executes the method. Even if an object is thread-safe and lives in an MTA, contrary to your intuition perhaps, calls to the MTA-bound object originating from a thread in an STA still result in a thread switch. This is shown in Figure 4.7.

Many developers are puzzled by this. If an object is thread-safe and lives in an MTA that can house any number of threads (and these threads can call directly into the object at any time), why can't an STA thread call directly into the object? The simple fact is that only those threads which have specifically entered the MTA by calling CoInitializeEx(0, COINIT_MULTITHREADED), or were already in the MTA (threads in the MTA's thread pool), are allowed direct access to the object. Threads in STAs are not allowed direct access. This is not to protect the MTA-residing object, which can obviously handle the STA thread, but rather to protect any objects that might be living in the calling STA. Problems can arise if the MTA object calls back into an STA object because the MTA object might do so on any thread. For more information on why the FTM compromises STA objects, see the following sidebar, "Danger of the FTM to STA Objects."

Cross-apartment calls result in a thread switch, even if the object is thread-safe and lives in an MTA.

Figure 4.7. Cross-apartment calls result in a thread switch, even if the object is thread-safe and lives in an MTA.

For the time being, suppose that we are certain the MTA object is not going to call back to anything in the STA. Furthermore, suppose you do, in fact, want the STA thread to make a direct invocation on the MTA-residing object without needing a thread switch. In other words, you want the behavior shown in Figure 4.8.

If you want threads in any apartment to directly access and invoke methods on an object in another apartment without a thread switch, the FTM will make this possible.

The FTM performs its magic by allowing the objects that use it to escape the process of marshaling interfaces between threads. Specifically, you continue to marshal your interfaces between threads using the CoMarshal APIs (or let COM+ do it for you when interfaces are passed as object method arguments in IDL), but the presence of the free-threaded marshaler causes the stream to contain an ordinary pointer to the interface. In effect, although you are calling the marshaling APIs as described in the previous section, "Marshaling Interfaces," but with the FTM, you ultimately up passing an ordinary interface pointer between threads.

Direct invocation on an object in an MTA from a thread in a STA.

Figure 4.8. Direct invocation on an object in an MTA from a thread in a STA.

Danger of the FTM to STA Objects

Normally, an STA thread monitors a message queue and does not ever block on a method call itself. Rather, this blocking is internally delegated to another hidden thread so that the primary STA thread can keep "pumping" the message loop and can continue to service method calls coming in for other objects in the STA. This is how it is possible for an STA object to call a method and still be able to receive a callback notification from the server object even while that method is still executing. If an object uses the FTM, however, the STA thread calling it will become directly involved in the method invocation and will no longer be paying attention to its message loop. The problems here can range from possible deadlock, to concurrent access, to STA objects that are no longer protected by the dynamic duo of the guardian STA thread and its method-serializing message queue. Thus, the FTM should only be used when the interaction between participating components is fully understood and the performance benefit clearly outweighs the dangers and uncertainty its use can bring about.

This might seem like a bad idea with a dangerous downside, but there are circumstances where you need this latitude. For example, if you want to write your own COM+ resource dispenser (discussed in Chapter 8, "Transactions"), you must use the FTM due to issues arising from COM+ contexts (discussed in Chapter 7). Basically, aside from facilitating the development of low-level components like resource dispensers, it has a simpler use—the FTM is an optimization available to authors of thread-safe objects (that do not fire any form of callback event) that are accessed from a client containing one or more STAs. Normally, COM picks up on the differing threading models between the client threads and object and inserts proxy-based protection during the inter-thread marshaling process. For example, if STA thread T1 marshals an interface and hands it to STA thread T2 which unmarshals it, T2 does not get a direct pointer to the object, but gets a proxy instead. This proxy allows COM to set up protection for the object, but this protection isn't always necessary. Because the object is happy in an MTA and can handle concurrent method calls from different threads, it is okay if client STAs do swap a direct interface pointer around.

Direct pointers are faster than proxies, and this can lead to increased performance. So, if the developer imbues his free-threaded object with the power of the free-threaded marshaler, performance can be improved. Always remember though, there is no such thing as a free lunch or a free marshaler. The FTM is never essential (except for certain utility components) and should not be used by objects that intto take advantage of COM+ services. In other words, if you are writing COM+ components, they should not use the FTM. The FTM can be a performance booster for traditional COM components that operate outside of COM+, but even then only use the FTM when you have worked with and mastered the intricacies of COM threading. You will find in the next section that the Thread Neutral Apartment (TNA) provides the same performance benefits as the FTM, but does so in a COM+-friendly way.

For the most part, the FTM is rarely employed by applications developers, it is more of a low-level tool for systems programmers. For example, if you are writing a Resource Dispenser (RD) like the ODBC driver manager, and are handing out connections (or other resources) to COM+ objects in different contexts (we will talk more about contexts in Chapter 7 and RDs in Chapter 8), you may need to use the FTM to "get around" traditional COM marshaling rules that need to be violated in this special case.

In scenarios where you don't need to break the rules, don't; by using the FTM, you disqualify your component from participating in many of the services offered by COM+. Without getting into too much detail before I have a chance to properly introduce contexts in Chapter 7, the FTM makes it impossible for COM+ to properly associate your object with a context. And this, as we'll see, is critical if the object is to take advantage of COM+ services.

To use the FTM, you must aggregate it. Basically, aggregation is a way for COM objects to incorporate the functionality of other pre-existing, compiled COM objects. You might think this can be done through simple inheritance, but the idea is to incorporate an object that is already compiled You cannot inherit from assembly language or Java byte-code, so aggregation provides another way.

At this point, many texts get deep into the intricacies of aggregation. I, however, will relegate the details of such to the book's sample code. A simple way to look at aggregation is as a cross between inheritance and nested classes. It allows you to take two objects and compress them into what appears to the outside world as one. There is an inner object and an outer object. The outer object ultimately presents to the world, as its own, the interfaces of the inner object. The inner object exists in the belly of the outer object and does not necessarily know it is being aggregated (see Figure 4.9).

Obviously, there are all kinds of lifetime and QueryInterface() issues that arise, but an aggregatable component is equipped to handle them. Again, consult the book's sample code for more details. For now, just know that aggregation is not intended as a re-use mechanism. It exists for situations just like this, where a COM system object (like the FTM) needs to merge with a standard object (written by you, the developer) to imbue it with some new, system-oriented functionality.

Even though it sounds impressive, aggregating the FTM only requires one function call, one member variable and a modification to an object's QueryInterface( ) method. Listing 4.12 demonstrates how to aggregate the FTM in a simple object.

Simple view of aggregation.

Figure 4.9. Simple view of aggregation.

Example 4.12.  Aggregating the FTM with a Simple Function Call and Modification to the Class's QueryInterface

class COMCalc: public ICalc
{

public:
    LONG        m_cRef;
// IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    STDMETHOD(Add)(int x, int y, int*r);
    STDMETHOD(Subtract)(int x, int y, int*r);
//FTM Additions below
    IUnknown * pMarshaler; //add a member variable

    COMCalc()
    {
        //Create and aggregate the FTM
//error checking ommitted for brevity
        CoCreateFreeThreadedMarshaler(static_cast<IUnknown*>(this),
                                      &pMarshaler);
                                  
    };
    ~COMCalc()
    {

       pMarshaler->Release();

    }


};

STDMETHODIMP COMCalc::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_ICalc)
    {
        *ppv = static_cast<ICalc*>(this);
    }
    else if(riid==IID_IMarshal)
    {
        pMarshaler->QueryInterface(riid, ppv);
    }
//And so on…

The Thread Neutral Apartment (TNA)

There is another facet to COM apartments and threading, new with Windows 2000. So far, we have discussed the STA and MTA. You learned that there is exactly one thread in one STA, but that an MTA can have many threads in it. You also learned that COM sets up protection in the form of hidden windows for objects (apartments) that need it. Lastly, you learned that an apartment, be it STA or MTA, is just a concept, a way of grouping objects and the threads that manipulate them into domains of protection.

If I add a little more detail to the picture, the purpose of the TNA comes to light. There are many possible scenarios that can come about as the result of mixing of models and marshaling interface pointers between threads. Depending on the locality (COM+ library applications that are in-process or COM+ server applications that are in a surrogate), COM's protection schemes can result in threads having direct connections to objects, proxies to objects, or even proxies to proxies. I relegate most of this discussion to Appendix B, "COM+ Synchronization Through Activities." At this point, I only want to address the one, specific scenario that the TNA exists to solve—thread context switching.

In many cases, the thread that calls a method on an interface is not the thread that actually executes the code. At some point, a switch takes place. Consider the example of an STA thread (T1) that creates an STA object, obtains an interface pointer, and then marshals the interface pointer to another STA thread (T2) in the same process. When T2 unmarshals the interface pointer it obtained, T2 does not get a raw pointer; rather, it gets a proxy. Any calls that T2 makes to the proxy are delegated back to T1's STA, and T1 actually makes the call. T2 blocks (or seems to block), waits for the OS to schedule a thread context switch, and gives the green light to T1, which executes the code on behalf of T2. Waiting for a thread context switch is expensive in terms of time and resources.

The TNA exists to allow T2 to make the method call directly. The TNA is sometimes called the Rental Model, because T2 enters the TNA momentarily to execute the function. If this is sounding a lot like the FTM, the TNA and FTM are similar in that both allow the calling thread to actually execute the code of an object's method without requiring a thread switch. If this is the behavior you want, TNA is preferable because its services can be obtained declaratively, whereas the FTM requires additional code. Furthermore, and more importantly, an object in a TNA may have a consistent set of attributes (we'll see in Chapter 7 that this is known as a context) that is maintained during method calls from different threads. FTM objects, however, always borrow the context of the calling thread; and while this is important for utility objects like resource dispensers, it is deadly for ordinary COM+ objects that want to take advantage of COM+ services. Unlike the MTA and STA, the TNA does not have threads as permanent residents. Conceptually, it is an empty apartment where an object waits with its own sense of identity to be acted on by a thread that can come bursting through the door at any minute.

The Microsoft party line states that the TNA is said to be the preferred threading model for COM+ objects, except in situations where you need a UI, in which case an STA is preferable because you are already running a message pump. Of course, if a TNA allows any thread in at any time, it stands to reason that an object residing in a TNA must be thread-safe. This is true if you are writing an ordinary COM component that will not run in COM+. However, COM+ offers synchronization services that will only allow one thread into a TNA at one time. By externalizing synchronization, COM+ makes it possible for TNA objects to be built in languages that do not support multi-threading. Thus, the TNA offers the best of all worlds to a configured component: direct thread access from a caller in any apartment (like the FTM), a consistent context (unlike the FTM), and external synchronization. All these advantages together finally achieve the much anticipated "rental" model. The rental model is said to be employed when a component is marked as TNA and is configured to use synchronization.

Summary

A Win32 application can have more than one thread, and if it weren't for apartments, developers would be responsible for making their objects and COM DLL's thread-safe.

Apartments are a concept whereby threads and objects are grouped into domains of ownership. By associating threads and objects with a given apartment, if necessary, COM can protect objects by erecting additional proxies and/or serializing method calls made to them through a hidden window.

There are three types of apartments (MTA, STA, and TNA). Client threads and server objects can be of differing types, in which case COM protects thread-unsafe objects (those that require the STA) from concurrent access by multiple client threads.

Protection comes at a cost, however. The hidden serialization windows of the STA impose a great deal of overhead. Higher performance MTA scenarios demand more programming know-how, and there can still be overhead in the form of context switching. The TNA avoids context switching and does not need serialization windows. These advantages have enabled the TNA to become the preferred model for COM+ components.

When a developer wants to pass an interface from one thread to another, he must always do it by marshaling the interface via a call to CoMarshalInterthreadInterfaceInStream (or CoMarshalInterface). This gives COM the opportunity to set up the appropriate degree of protection for the object if necessary. Note, however, that an interface can be unmarshaled only once. To unmarshal an interface repeatedly, the GIT must be used.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.24.171