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).

 



Comments

  • Punten moeten worden wist door Dr Dre koptelefoon NBA

    Posted by mrswanzi on 06/06/2013 10:15pm

    [url=http://koptelefoon-monsterbeats.tumblr.com/]beats by dre[/url] Op drebeatskopen.com tref je een uitgebreid assortiment hoofdtelefoons aan van Beats By Dre. Een goed geluid gaat nu eenmaal hand-in-hand met een degelijk kwaliteitsmerk. Over de hele wereld genieten muziekliefhebbers, dj¡¯s en geluidspuristen van de hoge kwaliteit van dit geweldige stijlvolle merk en dat aantal stijgt steeds meer. Muzikaal genot wordt pas echt tot leven gebracht met een mooie, elegante en eigenwijze ontworpen koptelefoon. Beats By Dre heeft het voor elkaar gekregen om de koptelefoon te verheffen tot stijlicoon. Maar dan met behoud van de beste geluidskwaliteit, vergelijkbaar met niets minder dan studiokwaliteit. Je beleeft de muziek zoals de artiest het echt heeft bedoeld! [url=http://beatssolokopen.weebly.com/]beats solo kopen[/url] Met de urBeats van Monster Beats by Dr Dre gaat er een wereld van geluid voor je open. Klein van stuk, maar groots van geluid! Heeft u genoeg van het steeds vervangen van die in-ears Maak daar dan voor eens en voor altijd korte mette mee en haal de Beats in huis, en u hoort wat u al die tijd hebt gemist. Ze zijn zo gemaakt dat u al het buitengewone HD-audio materiaal van uw iPod, iPad of iPhone weer kunt horen. Het is ongelooflijk wat voor geweldig geluid er uit zo'n klein oortelefoontje kan komen. Als u eenmaal over de Beats beschikt wilt u nooit meer anders! [url=http://monsterbeats.webgarden.es/]beats by dre[/url] Voor diverse andere merken reden genoeg ook toenadering te zoeken bij een grote artiest. Mode zit van oudsher stevig verweven in de Amerikaanse hip hop en rap cultuur. De uitingen die artiesten dragen worden door (jonge) fans makkelijk geadopteerd. Dr. Dre geldt in deze cultuur nog altijd als een invloedrijk persoon. Door de samenwerking met de Amerikaanse rapper aan te gaan, legde Monster direct het fundament voor een succesvolle introductie van de Beats hoofdtelefoons. De aanwezigheid van Dr. Dre zorgde voor een fundamentele verandering in hoofdtelefoonland. Niet langer was het product een noodzaak, maar meer een accessoire. Beats by Dr. Dre Studio hoofdtelefoons: Kleurrijke kleur

    Reply
  • Steps by step

    Posted by uma_shankar21 on 07/12/2005 07:41am

    Hi Rama,
    
    Thanks for your article.Could u please explains step by step to create this project from the scratch.
    
    Regards,
    -Umashankar H.M

    Reply
  • Excellent!!!! Plz continue with ur valuble articles

    Posted by Legacy on 06/03/2003 12:00am

    Originally posted by: ALN SaiSrinivas

    Dear Ramakerishna,
    Really u did a wonderful job for beginers of COM Thraeding Models.Congratulations.Plz continue ur series of articles.

    Regards
    SaiSrinivas

    Reply
  • Good one

    Posted by Legacy on 03/19/2003 12:00am

    Originally posted by: Rami

    Thanks for your effort.

    Reply
  • an unbelievable article

    Posted by Legacy on 07/26/2002 12:00am

    Originally posted by: Paul Fred

    the article is so wonderful that i've never seen.

    Reply
  • Great example on COM Threading

    Posted by Legacy on 04/11/2002 12:00am

    Originally posted by: Karen Bui

    Thank Rama, for giving the example with great details.

    Reply
  • HELP

    Posted by Legacy on 12/13/2001 12:00am

    Originally posted by: Davroz_star

    I have just discovered "OleMainThreadWndName" on my computer, it appears when i connect to a webpage ....is this normal? sorry if i seem dull ...its cos i am :D

    ~Davroz_star@go.com~

    Reply
  • Getting a Feel of COM Threading Models

    Posted by Legacy on 11/28/2001 12:00am

    Originally posted by: Brad

    Good Job!

    Reply
  • nice one

    Posted by Legacy on 07/24/2001 12:00am

    Originally posted by: Faizal P

    nice one...

    • What is Thread , Why it is,When it has to be used and stopped in VC++

      Posted by jeyachandran_03 on 09/01/2004 07:14am

      I need All informations with samples, please send me as soon as possible.

      Reply
    Reply
  • Getting a Feel of COM Threading Models

    Posted by Legacy on 01/16/2001 12:00am

    Originally posted by: Ravi

    Good One.....exposes the practical aspects of threadin model and explains what 's hapen behind the scenes.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds