Introduction
More and more COM objects are written to be 'Both' threaded because it enables
creating the object in either a COM STA or MTA. If the thread that creates the 'Both'
threaded COM object is initialized to be in an STA by using CoInitialize()
,
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
or OleInitialize()
,
the object will be created in the same STA. If the thread is instead initialized to be in the
MTA by using CoInitializeEx(NULL, COINIT_MULTITHREADED)
, the object will be
created in the process's one and only MTA.
The developer of the object is thus forced to code for simultaneous access by multiple threads,
typically by using a Critical Section. Note that any global and static variables need to be
protected anyway. What I am presenting here optimizes the code that protects the per object
instance state. I also want to point out that if the object in question aggregates the Free
Threaded Marshaller (FTM), this technique should not be used.
The class CComSmartAutoCriticalSection
is an extremely simple class that wraps
a WIN32 CRITICAL_SECTION
. The definition of this class is as follows:
class CComSmartAutoCriticalSection
{
public:
CComSmartAutoCriticalSection()
{
if (SUCCEEDED(::CoInitialize(NULL)))
{
::CoUninitialize();
m_bInMTA = FALSE;
}
else
{
m_bInMTA = TRUE;
}
if (m_bInMTA)
::InitializeCriticalSection(&m_csSafeAccess);
}
~CComSmartAutoCriticalSection()
{
if (m_bInMTA)
::DeleteCriticalSection(&m_csSafeAccess);
}
void Lock()
{
if (m_bInMTA)
::EnterCriticalSection(&m_csSafeAccess);
}
void Unlock()
{
if (m_bInMTA)
::LeaveCriticalSection(&m_csSafeAccess);
}
private:
BOOL m_bInMTA;
CRITICAL_SECTION m_csSafeAccess;
};
The constructor detects whether the creating thread belongs to a COM STA or MTA and
initializes the private member variable m_bInMTA. The CoInitialize()
call will return S_FALSE if the calling thread belongs to an STA or RPC_E_CHANGED_MODE
if the calling thread belongs to an MTA. If the object is being created in the MTA, the
m_csSafeAccess critical section is initialized..
The destructor deletes the m_csSafeAccess critical section if the object is created in
the MTA.
The Lock()
and Unlock()
methods invoke the WIN32
EnterCirticalSection()
and LeaveCriticalSection()
APIs
respectively ONLY IF the object is created within the MTA.
Usage
Within the C++ class the implements your COM object, define a member variable of type
CComSmartAutoCriticalSection
.
class ATL_NO_VTABLE CSomeCOMClass : <inheritance list>
{
private:
CComSmartAutoCriticalSection m_csSafeAccess;
}
The implementation functions for the interface properties and methods should be wrapped
within the Lock()
and Unlock()
functions.
STDMETHODIMP CSomeCOMClass::SomeMethod()
{
HRESULT hResult = S_OK;
m_csSafeAccess.Lock();
...
m_csSafeAccess.Unlock();
return hResult;
}