Getting a Feel of COM Threading Models

The purpose of this article is to explain the various
COM threading models with the aid of a tutorial.

Before We Begin

For this tutorial following are required :-


  1. Visual C++ Version 5.0 or 6.0 (preferred)
  2. OLE/COM Object Viewer.
  3. Spy++ – Window Spying Utility that comes with VC.


Before we proceed further download the tutorial projects.  The
tutorial consists of a main workspace "COMThreading". This has three
projects as following :-











Server A COM DLL server application implemented in
ATL. It has one object supporting a single non-dual interface
IServer. The interface IServer has a single method – SimpleMethod
which just displays a message box.
ServerPS A simple Win32 DLL project for building the
proxy-stub DLL from the files generated from MIDL compiler.
Client        A simple console application that uses the
Server Object. We would be dealing almost entirely with this project
in this tutorial.


Unzip the "Comthreading.zip" file and open the
workspace "COMThreading.dsw" in VC++. All the three projects described
above must be visible in the workspace.


1. To start with set "Server" as
the active project and build it. This builds and register
"Server.dll".

2. Once the Server project is build. It is
time to open OLE/COM Object Viewer and see what we have got. From the Tools
menu is VC or otherwise open OLE/COM object viewer.

3. Next In the View menu of OLE/COM object
viewer make sure that Expert Mode is checked. Under object classes expand
"All Objects". Scroll down until you see "ServerApp
Class" item in the tree view. It should look something like the screen
shot shown below. Note that it shows the interface IUnknown only and not
IServer. This is because IServer is not a recognized interface in the
Registry.

OLE/COM Object Viewer

The First Client Application


4. Next it is the time to build the client
application. In the Client application I have used compiler COM support for
simplicity. This enables us to create a ServeApp object and invoke it’s
SimpleMethod conveniently using smart interface pointers.
This is
the code for the client application.

#include <windows.h>
#pragma hdrstop

#import "Server.tlb" no_namespace
#include "MyComutl.h"

int main(int , char** )
{
CComInit cominit(COINIT_APARTMENTTHREADED);

IServerPtr spServer(__uuidof(ServerApp));

spServer->SimpleMethod();

return 0;

}


CComInit is a class whose constructor initializes COM
calling CoInitializeEx in the constructor and uninitializes COM by calling
CoUninitialize in the destructor. Thus in the main function destructor of
IServerPtr for spServer (which Releases spServer) is called before the
destructor of of CComInit for cominit. After cominit has been declared, an
object of ServerApp and the interface pointer to IServer is obtained in the
smart pointer spServer. Finally the method SimpleMethod is invoked.


5. Now it’s the time to run the client
application. Place a breakpoint in the first line where an object of
CComInit is being declared and do a "Debug – Go" (or press
F5).

6. Leaving the execution at the breakpoint
(don’t step over), open Spy++ (from Tools menu or in any other way). From
the Spy Menu of Spy++ select processes. Under the tree of processes expand
"Client". The screen would look something like this.

Image of Spy++ Processes listings

Note that Spy++ has recognized the Client process. The
child item ‘0000005CC "Client" tty’ that is shown is the window
handle, the title and the window class name of the console window belonging to
the process.


6. Now step over a line of code in the
debugger. After stepping over switch back to Spy++ and refresh the view by
pressing F5. Under the Client tree item the child items look like the
following now

Spy++ after stepping through debugger

Note that a new window (hidden) named
"OleMainThreadWndName" of class "OleMainThreadWndClass" has
been created in the process due to a call to CoInitializeEx(NULL,
COINIT_APARTMENTTHREADED). In a process whichever thread calls
CoInitialize(NULL) or   CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) first
is called the Main STA (Single Threaded Apartment). (I will delve into
terminologies later). Thus the window name "OleMainThreadWndName" is
significant.


7. Just do a "Debug Go" on the
rest of the code. Dismiss the message box that is displayed by pressing OK
button.

The significant point which we observed going through
all this ordeal was that the COM runtime (basically routines in OLE32.dll)
create a hidden window when COM is initialized using CoInitialize(Ex). The
purpose of this window would become clear in the next steps.


Client Application 2 – Entering the MultiThreaded
World


In the world COM the component and the client may be
created independently. Now suppose the client application uses multiple threads
but the poor component which was built with the assumption that there would be
only single thread accessing the component anytime, and the data and routines
were not protected taking threading into consideration. If the client tries to
access the component using multiple threads the component code is bound to fail
or may lead to weird behaviours due to this. Luckily, COM helps to protect the
code in such a case.


In simple DLL based server the interface pointers can
ofcourse be passed to different threads, but if the DLL is not built to handle
multiple threads there would be failures, in some cases it may even lead to bugs
hard to detect because the code may fail randomly due to synchronization issues.
How can a COM interface obtained from an object created in one thread (more
precisely apartment, for the present moment let us leave it as thread and not go
in terminologies) can be safely used by another thread in the same process? COM
provides an answer – "Marshal the interface from one thread to the other
thread". Though marshaling used to be associated with RPC communications
(and it still is), in the current context marshaling has something to do with
ensuring thread safety. I am purposely being vague here as it would be clear as
we see practically what happens.


What exactly marshaling does and how it ensures thread
safety? We will this see next.


8. For marshaling to work we need to build
and register the proxy stub dll. So build "ServerPS" project. This
would automatically register the proxy stub dll. To actually see that the
proxy stub dll has been registered, sitch back once again  to OLE/COM
Object Viewer and now it can be seen that the OLE/COM Object Viewer shows
IServer interface when the "ServerApp Class" item is
expanded.


This does all the background preparation to
be able to marshal interfaces from one thread to another.

9. Now we need to add some code in the
client application to handle multiple threads. The entire code that we will
be going to add is in the files mycomutl.cpp and client1.cpp files. All that
needs to be done is to cut and paste code from client1.cpp to client.cpp or
remove client.cpp from the project and add client1.cpp whichever way is
convenient.

10. Each thread in the process that needs
to call COM functions should Initialize COM using CoInitialize(Ex) and
unintialize it before it terminates. To do this I have a simple helper
function BeginCOMThread (mycomutl.cpp) like this :-


HANDLE BeginCOMThread(DWORD dwTM, THREADFN pfnStart)
{
unsigned int dwId;

COMThreadingHelper* pCT = new COMThreadingHelper;

pCT->dwTM = dwTM;
pCT->pfnStart = pfnStart;

return (HANDLE) _beginthreadex(
NULL, //Security
0, //Stack Size
ThreadProc, //Start Address
(void*)(pCT), //Parmeter
0, //Creation Flag
&dwId //Thread Id
);
}

Where COMThreadingHelper struct has been defined as


typedef void (*THREADFN)();

struct COMThreadingHelper
{
DWORD dwTM; //Threading model
THREADFN pfnStart;
};

This defintion is in an unnamed namespace in
MyComutl.cpp. This is the C++ way of making functions and structs local to a
file.

Finally the function ThreadProc has been defined as


unsigned int _stdcall ThreadProc(LPVOID pParam)
{
COMThreadingHelper* pCT = reinterpret_cast<COMThreadingHelper*>(pParam);

//Initialize COM using the threading model passed in
CComInit cominit(pCT->dwTM);

//call the user function
(pCT->pfnStart)();

delete pCT;

return 0;
}

A point worth mentioning is that C runtime function
_beginthreadex has been used instead of Win32 function CreateThread. The main
reason behind this is that C runtime maintains thread specific global data like
errno, etc. By using _beginthreadex it is ensured that thread specific data of C
Runtime is maintained. This would not be done if Win32 CreateThread has been
used.

11. Now we are ready with a function that
creates a thread with COM initialized. So let us now understand how to
marshal IServer interface pointer from one thread to another. This is done
by one of the longest function (by name) in OLE32 API
CoMarshalInterThreadInterfaceinStream. Now the main function looks like
this



int main(int , char** )
{
CComInit cominit(COINIT_APARTMENTTHREADED);

IServerPtr spServer(__uuidof(ServerApp));

spServer->SimpleMethod();

//marsahl the spServer Interface pointer
HRESULT hr = CoMarshalInterThreadInterfaceInStream(__uuidof(IServer), //The IID of inteface to be marshaled
spServer, //The interface pointer
&g_pStm //IStream pointer
);

//Begin a thread in an STA
HANDLE hThread = BeginCOMThread(COINIT_APARTMENTTHREADED, AThread);

//Wait for the thread to finish execution
//A thread handle is signaled is thread execution
//is complete
WaitForSingleObject(hThread, INFINITE);

//_endthreadex doesnot close handle unlike _endthread
//When th efunction called by _beginthreadex completes
//execution _endthreadex is called automatically
//Therefore we need to Close the handle of the thread
CloseHandle(hThread);

return 0;

}

An Global variable g_pStm of type IStream* has been
defined. This is used by COM functions for marshaling. This stream pointer is
effective only for a single marshaling.

Since, the main application should not exit before the
worker thread we have created, we have to use WaitForSingleObject function.

The AThread function which obtains the marshaled
interface looks like this



void AThread()
{
//This functions executes in a separate thread
IServerPtr spServer;

HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStm,
__uuidof(IServer),
reinterpret_cast<LPVOID*>(&spServer));

spServer->SimpleMethod();
}

12. Now place a breakpoint just at the
beginning of AThread function. And run the debugger. As soon as the
execution stops at the AThread function open the Spy++ application again and
observe the client process again. This time it should look something like
this :-

Spy++ Image after thread has been created

This time Spy++ shows both the threads. 


13. Step over to next line and find that
after a call to CoGetInterfaceAndReleaseStream  the value of the
spServer pointer is valid. Step over to the next line. What happens? The
application hangs. All that can be done is to select "stop
debugging" from the "Debug" menu.

If the call to spServer->SimpleMethod had been a
direct call to function in the dll, the application should not hang. But we have
marshaled the interface to the other thread and we are accessing the marshaled
interface in the other thread. A call to spServer->SimpleMethod no longer is
direct but it has to pass through system code till it reaches the actual
SimpleMethod function in Server.dll.


Since the interface pointer which was obtained by
marshaling process actually belongs to an object created in another thread and
the object cannot handle multiple threads, the SimpleMethod function in the
object should be called from the same thread that created the object. This means
that the thread FFF81C03 (it may be different on your machine) should somehow
tell thread FFFD8147 that it needs to call the function SimpleMethod from it.
The execution context has to be changed. How can this be done? This is not
difficult given that a hidden window has been created by thread FFFD8147. All
that thread FFF81C03 needs is to SendMessage to the hidden window. As the window
messages are delivered in the respective threads this method would change the
execution context to thread FFFD8147. But we have not implemented any message
loop in FFFD8147 and instead we are waiting for the other thread to complete
execution. Thus there is a deadlock.


Therefore, we need to implement a message loop in the
main thread (FFFD8147) more precisely we need to dispatch window messges to the
respective window procedures. But we also need to wait for execution of thread
FFF81C03 to complete. Luckily, Win32 API provides with a function
MsgWaitForMultipleObjects , that returns when either a
message is there in the message queue of the thread or the objects have been
signalled. For details of this function please consult the documentation.


14. Now, the main function need to be
modified to incorporate message dispatching. The WaitForSingleObject
function now need to be replaced with the following code (you can cut and
paste from Client2.cpp)


//Wait for the thread to finish execution
//A thread handle is signaled if thread execution
//is complete
for(;;)
{
DWORD dwRet = MsgWaitForMultipleObjects(
1, //Count of objects
&hThread, //pointer to the array of objects
FALSE, //Wait for all objects?
INFINITE, //Wait How Long?
QS_ALLINPUT //Wait for all messges
);

//This means that the object is signaled
if (dwRet != WAIT_OBJECT_0 + 1)
break;

//Remove the messages from the queue
MSG msg;

while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
{
//Not essential
TranslateMessage(&msg);
//Let the windowproc handle the message
DispatchMessage(&msg);
}
}


15. Placing the breakpoint at AThread
function again start the program again in the debugger. When the exceution
stops open Spy++ and expand the client tree item again. It would look almost
the same as in the previous case. Now place a breakpoint in
CServerApp::SimpleMethod. This is in the Server project ServerApp.cpp file.
Just allow the debugger to continue execution and the exceution would stop
at the SimpleMethod function. Now just see the call stack to get an idea of
how the execution reached this point. The call stack would look something
like this :-


Call Stack


As is obvious from the call stack the execution reached
this function through routines in Kernel32.dll, rpcrt4.dll and ole32.dll instead
of being called directly from the client application. This is what is done by
marshaling. For a method call to even an in-proc server the execution has to go
through system level code. All this is being done to ensure thread safety to the
component’s methods.


16. Next keeping the breakpoint at the same
place in AThread function rerun the application again in the debugger. 
After the execution passes through CoGetInterfaceAndReleaseStream open Spy++
again. This time it would appear something like the following figure
:-

Spy++ Image

 


The difference is that now both the main thread and the
thread running in the function AThread (FFFAFA97) have windows created in them.
In the other thread there is a hidden window of the same class but a different
title "OLEChannelWnd". It would be good to experiment what messages
are being send to each of these windows. Spy++ again helps us, just right click
on each of the windows and select messages from the popup menu. Now any messges
being send to these windows would be shown by Spy++.


17. Let the application finish execution.
Now revert back to Spy++ and you will find the message log of the messages.
It appears something like the following.

Messages in Spy++ window

Note the two WM_USER messages. The wParam of the two
messages can immediately be recognized as the handle of the other thread. So,
this becomes clear that COM sends WM_USER messages to the hidden window
associated with the thread were the object belongs, if the objects’ method is
invoked from another thread through a marshaled interface. This is all done by
the marshaling code residing mainly in rpcrt4.dll.

In this article I primarily focussed on single interface
of a single object being accessed by different threads. There are many cases and
concepts (specially that of an Apartment) which I plan to cover as separate
articles in the same seris, depending on the feedback. Please give your feedback as how this tutorial
really is (whether it is useful or it is junk).

 

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read