Blog Post

Things You Thought You Knew - Current COM Apartment Model

How to get the COM concurrency model for the current thread.

Things You Thought You Knew - Current COM Apartment Model - How to get the COM concurrency model for the current thread.

Intro

You might have seen many times the following APIs that were used to initialize the concurrency model for the Component Object Model, before the application starts using COM:

C++
CoInitialize(NULL);
or:
C++
CoInitializeEx(0, COINIT_MULTITHREADED);
or even a newer version:
C++
RoInitialize(RO_INIT_MULTITHREADED);

All these functions are required to be called once per thread to initialize COM and to specify the concurrency model for interactions between the COM objects in a thread. By specifying the concurrency model developers tell to the COM whether its objects will be used in a single-threaded, or in a multi-threaded environment.

But how do you retrieve the current thread concurrency model if you need to know it later, or if you didn't initialize the process?

Internals

Surprisingly, Microsoft decided not to expose an API to retrieve the current thread concurrency model, which (not to confuse things) they also call "thread apartment model", by the way. 😂 Maybe they thought that there's no need for the programmers to know this, or they simply didn't care to document it. We will probably never know.

What is evident though is that the COM apartment is stored as an internal object, called CObjectContext, pointer to which is placed into TEB::ReservedForOle for the thread, that is actually somewhat documented:

To access the value of the ReservedForOle member, call CoGetContextToken.

The TEB::ReservedForOle is set to the pointer of the initialized COM apartment object when you call one of the CoInitialize* class of functions. Obviously, if you try to use any other COM functions for the thread where TEB::ReservedForOle is NULL, it will return an error CO_E_NOTINITIALIZED.

The CObjectContext itself implements several interfaces, and is roughly declared as such:

C++
class CObjectContext : public IObjContext,
	public IMarshalEnvoy,
	public IMarshal,
	public IComThreadingInfo,
	public IContextCallback, 
	public IAggregator,
	public IGetContextId
{
	//...
	LONG dwRefCount = 1;

	CObjectContext()
	{
		NtCurrentTeb()->ReservedForOle = this;
	}

	~CObjectContext()
	{
		NtCurrentTeb()->ReservedForOle = 0;
	}
};

This undocumented object is used pretty much for most calls within other COM functions, and thus obviously has to be created before any other COM object. A call to the CoInitialize*-class of functions creates this object in memory. It has an internal reference counter, so any subsequent calls to CoInitialize*/CoUninitialize will technically AddRef/Release that internal counter. And the CObjectContext will be destroyed when its reference count reaches zero.

That is why Microsoft has this to say in the documentation:

... each successful call to CoInitialize or CoInitializeEx, including any call that returns S_FALSE, must be balanced by a corresponding call to CoUninitialize.

To retrieve the reference to CObjectContext from the TEB, Microsoft instructs developers to use the CoGetContextToken function.

The question though is why is it declared as this:

C++
HRESULT CoGetContextToken(
	ULONG_PTR *pToken
);

and not as this?

C++
HRESULT CoGetContextToken(
	IObjContext** ppObjCtx
);

Which would be more logical, if you think about it.

The Resulting Function

And finally, here's the function to retrieve the current COM apartment model, or the COM concurrency model for the current thread:

C++
HRESULT GetCoConcurrencyModel(APTTYPE* pOutType)
{
	HRESULT hr;

	union {
		ULONG_PTR Token;
		IObjContext* pObjCtx;
	};

	APTTYPE AptType = APTTYPE_CURRENT;

	if (0 <= (hr = CoGetContextToken(&Token)))
	{
		IComThreadingInfo* pComInfo;

		if (0 <= (hr = pObjCtx->QueryInterface(IID_PPV_ARGS(&pComInfo))))
		{
			hr = pComInfo->GetCurrentApartmentType(&AptType);

			pComInfo->Release();
		}

		// pComInfo->Release();		//Note that we DO NOT need to call Release as CoGetContextToken does not do AddRef internally!
	}

	if (pOutType)
		*pOutType = AptType;

	return hr;
}

Our GetCoConcurrencyModel function will return a result code that must be compared with the SUCCEEDED macro for correctness. And if it is correct, then the pOutType variable will receive the current thread apartment model as the APTTYPE.

Note that Microsoft did not declare the APTTYPE correctly. It should have been declared as such:
C++
typedef enum _APTTYPE
{
	APTTYPE_CURRENT	= -1,
	APTTYPE_STA	= 0,			//A single-threaded apartment
	APTTYPE_MTA	= 1,			//A multithreaded apartment
	APTTYPE_NA	= 2,			//A neutral apartment
	APTTYPE_MAINSTA	= 3			//The main single-threaded apartment
} APTTYPE;

Related Articles