When a class is marked ThreadingModel=“both,” it is indicating that its instances and class object can safely reside in either an STA or an MTA. However, according to the rules of COM, any given instance will reside on only one apartment. If an object implementor has gone to the length of ensuring that an object can safely reside in an MTA, it might be the case that the object does not care about apartments at all. Such an object could be accessed concurrently not only by multiple threads in the MTA but also by non-MTA-based threads (e.g., threads that are executing in an STA). However, it is impossible for clients to know that such access is safe for a particular object, so all cross-apartment interface pointer sharing must be established using an explicit marshaling technique. This means that access to an in-process object will be via ORPC calls unless the caller is executing in the apartment that originally created the object.
Unlike clients, objects do know about their relationship to apartments, concurrency, and reentrancy. Objects that are satisfied with ORPC calls when accessed from multiple apartments in the same process get this behavior by default. However, an object that is not satisfied with ORPC-based access has the opportunity to bypass this by implementing custom marshaling. Custom marshaling could be used fairly easily to bypass the stub manager and simply serialize a raw pointer to the object into the marshaled object reference. When using this technique, the custom proxy implementation could simply read the raw pointer from the marshaled object reference and pass it to the caller in the importing apartment. The client threads would still pass the interface pointer across the apartment boundary by calling CoMarshalInterface/ CoUnmarshalInterface either explicitly or implicitly; however, the object would conspire with a custom proxy to simply pass a raw pointer to the actual object. Although this technique will work perfectly for intraprocess marshaling, it will fail miserably for interprocess marshaling. Fortunately, the object implementation can simply delegate to the standard marshaler for any marshaling context other than MSHCTX_INPROC.
Because the behavior just described is useful to a large class of objects, COM provides an aggregatable implementation of IMarshal that accomplishes exactly what was described. This implementation is called the FreeThreaded Marshaler (FTM) and can be created using the CoCreateFreeThreadedMarshaler API call:
HRESULT CoCreateFreeThreadedMarshaler(
[in] IUnknown *pUnkOuter,
[out] IUnknown **ppUnkInner);
A class that wishes to use the FTM simply aggregates an instance either at initialization time or on demand at the first QueryInterface request for IMarshal. The following class preinstantiates the FTM at construction time:
class Point : public IPoint {
LONG m_cRef; IUnknown *m_pUnkFTM;
long m_x; long m_y;
Point(void) : m_cRef(0), m_x(0), m_y(0) {
HRESULT hr = CoCreateFreeThreadedMarshaler(this,
&m_pUnkFTM);
assert(SUCCEEDED(hr));
}
virtual ~Point(void) { m_pUnkFTM->Release(); }
};
The corresponding QueryInterface implementation would simply request the IMarshal interface from the FTM:
STDMETHODIMP Point::QueryInterface(REFIID riid, void **pp) {
if (riid == IID_IUnknown || riid == IID_IPoint)
*ppv = static_cast<IPoint*>(this);
else if (riid == IID_IMarshal)
return m_pUnkFTM->QueryInterface(riid, ppv);
else
return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
Once the FTM is in place, whenever references to Point objects are marshaled across intraprocess apartment boundaries, no proxies will ever be used. This applies to explicit calls to CoMarshalInterface/CoUnmarshalInterface, as well as when references to Point objects are passed as method parameters to intraprocess proxies to non-Point objects.
The FTM consumes at least 16 bytes of memory. Because many in-process objects are never used outside their original apartment, preallocating the FTM may not be the best use of available resources. It is highly likely that the object will already have some thread synchronization primitive. If this is the case, then the FTM can be lazy-aggregated at the first QueryInterface for IMarshal. To achieve this, assume the following class definition:
class LazyPoint : public IPoint {
LONG m_cRef; IUnknown *m_pUnkFTM;
long m_x; long m_y;
LazyPoint(void) : m_cRef(0),m_pUnkFTM(0),m_x(0),m_y(0) {}
Virtual ~LazyPoint(void)
{ if (m_pUnkFTM) m_pUnkFTM->Release(); }
void Lock(void); // acquire object-specific lock
void Unlock(void); // release object-specific lock
: : :
};
Based on the preceding class definition, the following QueryInterface implementation will correctly aggregate the FTM on demand:
STDMETHODIMP Point::QueryInterface(REFIID riid, void **ppv) {
if (riid == IID_IUnknown || riid == IID_IPoint)
*ppv = static_cast<IPoint*>(this);
else if (riid == IID_IMarshal) {
this->Lock();
HRESULT hr = E_NOINTERFACE; *ppv = 0;
if (m_pUnkFTM == 0) // acquire FTM first time through
CoCreateFreeThreadedMarshaler(this, &m_pUnkFTM);
if (m_pUnkFTM != 0) // by here, FTM is acquired
hr = m_pUnkFTM->QueryInterface(riid, ppv);
this->Unlock();
return hr;
}
else
return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
The disadvantage of this approach is that all QueryInterface requests for IMarshal will be serialized; however, if IMarshal is never requested, then fewer resources will be acquired.
Given the relative ease of using the FTM, it is interesting to consider when the FTM is inappropriate. Certainly objects that can live only in single-threaded apartments should not use the FTM, as they are highly unlikely to be expecting concurrent access. This does not mean, however, that all objects capable of running in the MTA should use the FTM. Objects that have interface pointers as data members must use caution when using the FTM. Consider the following class that uses other COM objects to perform its operations:
class Rect : public IRect {
LONG m_cRef;
IPoint *m_pPtTopLeft;
IPoint *m_pPtBottomRight;
Rect(void) : m_cRef(0) {
HRESULT hr = CoCreateInstance(CLSID_Point, 0,
CLSCTX_INPROC, IID_IPoint, (void**)&m_pPtTopLeft);
assert(SUCCEEDED(hr));
hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC,
IID_IPoint, (void**)&m_pPtBottomRight);
assert(SUCCEEDED(hr));
}
: : :
}
Assume that the class Rect is an in-process class and is marked Threading-Model=“Both.” The constructor for a given Rect object will always execute in the apartment of the thread that calls CoCreateInstance (CLSID_Rect). This means that the two calls to CoCreateInstance (CLSID_Point) will also execute in the client’s apartment. The rules of COM indicate that the data members m_pPtTopLeft and m_pPtBottomRight can be accessed only from the apartment that executes the CoCreateInstance calls.
It is likely that at least one of the Rect methods uses the two interface pointer data members to perform its work:
STDMETHODIMP Rect::get_Area(long *pn) {
long top, left, bottom, right;
HRESULT hr = m_pPtTopLeft->GetCoords(&left, &top);
assert(SUCCEEDED(hr));
hr = m_pPtBottomRight->GetCoords(&right, &bottom);
assert(SUCCEEDED(hr));
*pn = (right – left) * (bottom – top);
return S_OK;
}
If the class Rect were to use the FTM, then it would be possible to invoke this method from apartments other than the apartment that made the initial CoCreateInstance calls. Unfortunately, this would cause the get_Area method to violate the rules of COM, because the two interface pointer data members are valid only in the original apartment. If the Point class also happened to use the FTM, then this would technically not be a problem. However, in general, clients (such as the Rect class) should not make assumptions about this very specific implementation detail. In fact, if the Point objects did not use the FTM and happened to be created in a different apartment due to ThreadingModel incompatibilities, the Rect object would be holding pointers to proxies. Proxies are notorious for enforcing the rules of COM and correctly return RPC_E_WRONG_THREAD when accessed from the wrong apartment.
This leaves the Rect implementor with one of two choices. One choice would be not to use the FTM and simply accept the fact that when clients pass Rect object references between apartments, ORPC will be used to access instances of class Rect. This is certainly the easiest solution, as it involves no additional code and will simply work without requiring any thought. The other choice is no longer to keep raw interface pointers as data members but instead to keep some marshaled form of an interface pointer as a data member. This is exactly what the Global Interface Table is intended for. To implement this approach, the Rect class would keep DWORD cookies as data members in lieu of raw interface pointers:
class SafeRect : public IRect {
LONG m_cRef; // COM reference count
IUnknown *m_pUnkFTM; // cache for FTM lazy aggregate
DWORD m_dwTopLeft; // GIT cookie for top/left
DWORD m_dwBottomRight; // GIT cookie for bottom/right
: : :
}
The constructor would still create two instances of Point, but instead of holding the raw pointers, the constructor would register the interface pointers with the global interface table:
SafeRect::SafeRect(void) : m_cRef(0), m_pUnkFTM(0) {
// assume ptr to GIT is initialized elsewhere
extern IGlobalInterfaceTable *g_pGIT; assert(g_pGIT != 0);
IPoint *pPoint = 0;
// create instance of class Point
HRESULT hr = CoCreateInstance(CLSID_Point, 0,
CLSCTX_INPROC, IID_IPoint, (void**)&pPoint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
hr = g_pGIT->RegisterObjectInGlobal(pPoint, IID_IPoint,
&m_dwTopLeft);
assert(SUCCEEDED(hr));
pPoint->Release(); // reference is now held in GIT
// create instance of class point
hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC,
IID_IPoint, (void**)&pPoint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
hr = g_pGIT->RegisterObjectInGlobal(pPoint, IID_IPoint,
&m_dwBottomRight);
assert(SUCCEEDED(hr));
pPoint->Release(); // reference is now held in GIT
}
Note that as long as the interface pointer is registered in the GIT, no additional references must be held by the interface pointer user.
Once the class has been converted to use the GIT instead of raw interface pointers, it must unmarshal a new proxy in each method call that needs access to the registered interfaces:
STDMETHODIMP SafeRect::get_Area(long *pn) {
extern IGlobalInterfaceTable *g_pGIT; assert(g_pGIT != 0);
// unmarshal the two interface pointers from the GIT
IPoint *ptl = 0, *pbr = 0;
HRESULT hr = g_pGIT->GetInterfaceFromGlobal(m_dwPtTopLeft,
IID_IPoint, (void**)&ptl);
assert(SUCCEEDED(hr));
hr = g_pGIT->GetInterfaceFromGlobal(m_dwPtBottomRight,
IID_IPoint, (void**)&pbr);
// use temp ptrs to implement method
long top, left, bottom, right;
hr = ptl->GetCoords(&left, &top);
assert(SUCCEEDED(hr));
hr = pbr->GetCoords(&right, &bottom);
assert(SUCCEEDED(hr));
*pn = (right — left) * (bottom — top);
// release temp ptrs.
ptl->Release();
pbr->Release();
return S_OK;
}
Because the implementation of SafeRect uses the FTM, it is pointless to try to keep the unmarshaled interface pointers across method invocations, because it is not known whether the next method invocation will happen in the same apartment.
The GIT will keep any registered interface pointers alive until they are explicitly removed from the GIT. This means that the SafeRect class must explicitly revoke the GIT entries for its two data members:
SafeRect::~SafeRect(void) {
extern IGlobalInterfaceTable *g_pGIT; assert(g_pGIT != 0);
HRESULT hr=g_pGIT->RevokeInterfaceFromGlobal(m_dwTopLeft);
assert(SUCCEEDED(hr));
hr = g_pGIT->RevokeInterfaceFromGlobal(m_dwBottomRight);
assert(SUCCEEDED(hr));
}
Removing an interface pointer from the GIT releases any references held on the object.
Note that using the GIT and FTM together implies that many, many calls to the GIT will be made to create temporary interface pointers for use in each individual method. Although the GIT is optimized to support this exact usage pattern, the code is still tedious. The following simple C++ class wraps the usage of GIT cookies behind a convenient type-safe interface:
template <class Itf, const IID* piid>
class GlobalInterfacePointer {
DWORD m_dwCookie; // the GIT cookie
// prevent misuse
GlobalInterfacePointer(const GlobalInterfacePointer&);
void operator =(const GlobalInterfacePointer&);
public:
// start as invalid cookie
GlobalInterfacePointer(void) : m_dwCookie(0) { }
// start with auto-globalized local pointer
GlobalInterfacePointer(Itf *pItf, HRESULT& hr)
: m_dwCookie(0) { hr = Globalize(pItf); }
// auto-unglobalize
~GlobalInterfacePointer(void)
{ if (m_dwCookie) Unglobalize(); }
// register an interface pointer in GIT
HRESULT Globalize(Itf *pItf) {
assert(g_pGIT != 0 && m_dwCookie == 0);
return g_pGIT->RegisterInterfaceInGlobal(pItf,
*piid, &m_dwCookie);
}
// revoke an interface pointer in GIT
HRESULT Unglobalize(void) {
assert(g_pGIT != 0 && m_dwCookie != 0);
HRESULT hr=g_pGIT->RevokeInterfaceFromGlobal(m_dwCookie);
m_dwCookie = 0;
return hr;
}
// get a local interface pointer from GIT
HRESULT Localize(Itf **ppItf) const {
assert(g_pGIT != 0 && m_dwCookie != 0);
return g_pGIT->GetInterfaceFromGlobal(m_dwCookie,
*piid,(void**)ppItf);
}
// convenience methods
bool IsOK(void) const { return m_dwCookie != 0; }
DWORD GetCookie(void) const { return m_dwCookie; }
};
#define GIP(Itf) GlobalInterfaceTable<Itf, &IID_##Itf>
Given this class definition and macro, the SafeRect class now holds GlobalInterfacePointers instead of raw DWORDs:
class SafeRect : public IRect {
LONG m_cRef; // COM reference count
IUnknown *m_pUnkFTM; // cache for FTM lazy aggregate
GIP(IPoint) m_gipTopLeft; // GIT cookie - top/left
GIP(IPoint) m_gipBottomRight;// GIT cookie - bottom/right
: : :
}
To initialize the GlobalInterfacePointer member, the constructor (which executes in the object’s apartment) simply registers the managed pointers by calling the Globalize method on each GlobalInterfacePointer:
SafeRect::SafeRect(void) : m_cRef(0), m_pUnkFTM(0) {
IPoint *pPoint = 0;
// create instance of class Point
HRESULT hr = CoCreateInstance(CLSID_Point, 0,
CLSCTX_INPROC, IID_IPoint, (void**)&pPoint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
hr = m_gipTopLeft.Globalize(pPoint);
assert(SUCCEEDED(hr));
pPoint->Release(); // reference is now held in GIT
// create instance of class point
hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC,
IID_IPoint, (void**)&pPoint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
hr = m_gipBottomRight.Globalize(pPoint);
assert(SUCCEEDED(hr));
pPoint->Release(); // reference is now held in GIT
}
Methods that need access to the globalized pointers can import a local copy via the GlobalInterfacePointer’s Localize method:
STDMETHODIMP SafeRect::get_Top(long *pVal) {
IPoint *pPoint = 0; // local imported pointer
HRESULT hr = m_gipTopLeft.Localize(&pPoint);
if (SUCCEEDED(hr)) {
long x;
hr = pPoint->get_Coords(&x, pVal);
pPoint->Release();
}
return hr;
}
Note that because the FreeThreaded Marshaler is in use, the raw interface pointer cannot be cached and must be imported in each method invocation to avoid access from the wrong apartment.
The previous code fragment can be automated even further. Because most method invocations on the GlobalInterfacePointer class will be to localize a temporary pointer in a method invocation, the following class automates the importing of the temporary pointer and its subsequent release, much like a smart pointer:
template <class Itf, const IID* piid>
class LocalInterfacePointer {
Itf *m_pItf; // temp imported pointer
// prevent misuse
LocalInterfacePointer(const LocalInterfacePointer&);
operator = (const LocalInterfacePointer&);
public:
LocalInterfacePointer(const GlobalInterfacePointer<Itf,
piid>& rhs,HRESULT& hr) {
hr = rhs.Localize(&m_pItf);
}
LocalInterfacePointer(DWORD dwCookie, HRESULT& hr) {
assert(g_pGIT != 0);
hr = g_pGIT->GetInterfaceFromGlobal(dwCookie, *piid,
(void**)&m_pItf);
}
~LocalInterfacePointer(void)
{ if (m_pItf) m_pItf->Release(); }
class SafeItf : public Itf {
STDMETHOD_(ULONG, AddRef)(void) = 0; // hide
STDMETHOD_(ULONG, Release)(void) = 0; // hide
};
SafeItf *GetInterface(void) const
{ return (SafeItf*)m_pItf; }
SafeItf *operator ->(void) const
{ assert(m_pItf != 0); return GetInterface(); }
};
#define LIP(Itf) LocalInterfacePointer<Itf, &IID_##Itf>
Given this second C++ class, managing imported pointers becomes much simpler:
STDMETHODIMP SafeRect::get_Volume(long *pn) {
long top, left, bottom, right;
HRESULT hr, hr2;
// import pointers
LIP(IPoint) lipTopLeft(m_gipTopLeft, hr);
LIP(IPoint) lipBottomRight(m_gipBottomRight, hr2);
assert(SUCCEEDED(hr) && SUCCEEDED(hr2));
// use temp local pointers
hr = lipTopLeft->GetCoords(&left, &top);
hr2 = lipBottomRight->GetCoords(&right, &bottom);
assert(SUCCEEDED(hr) && SUCCEEDED(hr2));
*pn = (right – left) * (bottom – top);
return S_OK;
// LocalInterfacePointer auto-releases temp ptrs.
}
The GIP and LIP macros make using the GIT and FTM together much less cumbersome. Prior to the availability of the GIT, using the FTM in a class with interface pointers was far more difficult than any of the code shown in this section.