Although the WritePtr and ReadPtr code fragments from the previous section are fairly straightforward to implement, COM recognizes that most explicit calls to CoMarshalInterface will be used to pass an interface pointer from one thread to another in the same process. To simplify this task, COM provides two wrapper functions that implement the required boilerplate code around CoMarshalInterface and CoUnmarshalInterface. The COM API function CoMarshalInterThreadInterfaceInStream
HRESULT CoMarshalInterThreadInterfaceInStream(
[in] REFIID riid,
[in, iid_is(riid)] IUnknown *pItf,
[out] IStream **ppStm
);
provides a simple wrapper around CreateStreamOnHGlobal and CoMarshal-Interface, as shown here:
// from OLE32.DLL (approx.)
HRESULT CoMarshalInterThreadInterfaceInStream(
REFIID riid, IUnknown *pItf, IStream **ppStm) {
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, ppStm);
if (SUCCEEDED(hr))
hr = CoMarshalInterface(*ppStm, riid, pItf,
MSHCTX_INPROC, 0, MSHLFLAGS_NORMAL);
return hr;
}
COM also provides a wrapper around CoUnmarshalInterface:
HRESULT CoGetInterfaceAndReleaseStream(
[in] IStream *pStm,
[in] REFIID riid,
[out, iid_is(riid)] void **ppv
);
which is a very thin veneer over CoUnmarshalInterface:
// from OLE32.DLL (approx.)
HRESULT CoGetInterfaceAndReleaseStream(
IStream *pStm REFIID riid, void **ppv) {
HRESULT hr = CoUnmarshalInterface(pStm, riid, ppv);
pStm->Release();
return hr;
}
Neither of these two routines is especially enabling, but they are somewhat more convenient than their lower level counterparts.
The following code fragment could be used to pass an interface pointer to another apartment in the same process using a global variable to hold the
intermediate marshaled object reference:
HRESULT WritePtrToGlobalVariable(IRacer *pRacer) {
// where to write the marshaled ptr
extern IStream *g_pStmPtr;
// thread synchronization for read/write
extern HANDLE g_heventWritten;
// write marshaled object reference to global variable
HRESULT hr = CoMarshalInterThreadInterfaceInStream(
IID_IRacer, pRacer, &g_pStmPtr);
// signal other thread that ptr is now available
SetEvent(g_heventWritten);
return hr;
}
The corresponding code would correctly unmarshal the object reference into the caller’s apartment provided it is in the same process:
HRESULT ReadPtrFromGlobalVariable(IRacer * &rpRacer) {
// where to write the marshaled ptr
extern IStream *g_pStmPtr;
// thread synchronization for read/write
extern HANDLE g_heventWritten;
// wait for other thread to signal that ptr is available
WaitForSingleObject(g_heventWritten, INFINITE);
// read marshaled object reference from global variable
HRESULT hr = CoGetInterfaceAndReleaseStream(
g_pStmPtr, IID_IRacer, (void**)&rpRacer);
// MSHLFLAGS_NORMAL means no more unmarshals are legal
g_pStmPtr = 0;
return hr;
}
The preceding code is required when passing a pointer from one apartment to another.7 Note that to pass a pointer from a thread executing in an MTA or RTA to another thread executing in the same apartment, no marshaling calls are required. However, it is common practice for the writer of the interface pointer to call AddRef prior to handing off a copy to the reader thread. The reader thread would of course need to call Release when it is done using the pointer.
Note that the preceding reader code set the global variable g_pStmPtr to null after the unmarshal. This is because the object reference was marshaled using the MSHLFLAGS_NORMAL flag and can be unmarshaled only once. For many scenarios, this is not a problem. However, there are many scenarios in which it is desirable to marshal a pointer from one thread and have multiple worker threads unmarshal the interface pointer when it is needed. If the worker threads all run in the MTA, this is not a problem, as only one of the threads needs to perform the unmarshal on behalf of all of the threads running in the MTA. However, if the worker threads run in arbitrary apartments, this approach would not work, as each worker thread would need to unmarshal the object reference independently. Most developers turn to the MSHLFLAGS_TABLESTRONG flag at this point, hoping to marshal once and unmarshal as many times as necessary (once per apartment). Unfortunately, table marshaling (unlike normal marshaling) is not supported if the original pointer is a proxy, which is often the case, especially in distributed applications. To address this need, the Windows NT 4.0 Service Pack 3 release of COM introduced the Global Interface Table.
The Global Interface Table (GIT) is an optimization of CoMarshalInterface/ CoUnmarshalInterface that allows interface pointers to be accessed by all apartments in a process. COM internally implements one GIT per process. The GIT contains marshaled interface pointers that can be efficiently unmarshaled multiple times within the same process. This is the semantic equivalent of using table marshaling, but the GIT can be used with both objects and proxies. The GIT exposes the IGlobalInterfaceTable interface:
[ uuid(00000146-0000-0000-C000-000000000046),object,local ]
interface IGlobalInterfaceTable : IUnknown {
// marshal an interface into the GIT
HRESULT RegisterInterfaceInGlobal(
[in] REFIID riid,
[in, iid_is(riid)] IUnknown *pItf,
[out] DWORD *pdwCookie);
// destroy the marshaled object reference
HRESULT RevokeInterfaceFromGlobal(
[in] DWORD dwCookie);
// unmarshal an interface from the GIT
HRESULT GetInterfaceFromGlobal (
[in] DWORD dwCookie
[in] REFIID riid,
[out, iid_is(riid)] void **ppv);
}
Clients gain access to the GIT for their process by calling CoCreateInstance using the class CLSID_StdGlobalInterfaceTable. Each call to CoCreateInstance using this CLSID returns a pointer to the single GIT of the process. Like the IStream interface returned by CoMarshalInterThreadInterfaceInStream, interface pointers to the GIT can be accessed from any apartment without requiring marshaling.
To make an interface pointer available to all apartments in the process, the apartment that owns the interface pointer must register it with the GIT by calling the RegisterInterfaceInGlobal method. The GIT will return a DWORD to the caller that represents the pointer globally across all apartments in the process. This DWORD can be used from any apartment in the process to unmarshal a new proxy by calling the GetInterfaceFromGlobal method. This DWORD can be used to unmarshal proxies repeatedly until a call to RevokeInterfaceFromGlobal invalidates the global interface pointer. Applications that use the global interface table usually bind a single process-wide interface pointer at startup:
IGlobalInterfaceTable *g_pGIT = 0;
HRESULT InitOnce(void) {
assert(g_pGIT == 0);
return CoCreateInstance(CLSID_StdGlobalInterfaceTable, 0,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void**)&g_pGIT);
}
Once the global interface table is available, passing an interface pointer to another apartment simply means registering the pointer with the global interface table:
HRESULT WritePtrToGlobalVariable(IRacer *pRacer) {
// where to write the marshaled ptr
extern DWORD g_dwCookie;
// thread synchronization
extern HANDLE g_heventWritten;
// write marshaled object reference to global variable
HRESULT hr = g_pGIT->RegisterInterfaceInGlobal(
IID_IRacer, pRacer, &g_dwCookie);
// signal other thread that ptr is now available
SetEvent(g_heventWritten);
return hr;
}
The following code correctly unmarshals the object reference and can be called from any apartment in the same process:
HRESULT ReadPtrFromGlobalVariable(IRacer * &rpRacer,
bool bLastUnmarshal) {
// where to write the marshaled ptr
extern DWORD g_dwCookie;
// thread synchronization
extern HANDLE g_heventWritten;
// wait for other thread to signal that ptr is available
WaitForSingleObject(g_heventWritten, INFINITE);
// read marshaled object reference from global variable
HRESULT hr = g_pGIT->GetInterfaceFromGlobal(
g_dwCookie, IID_IRacer, (void**)&rpRacer);
// if we are the last to unmarshal, revoke the pointer
if (bLastUnmarshal)
g_pGIT->RevokeInterfaceFromGlobal(g_dwCookie);
return hr;
}
Note that a critical difference between these code fragments and the examples using CoMarshalInterThreadInterfaceInStream is that the GIT-based code supports unmarshaling more than one proxy.
7 It may seem odd that the global variable is an interface pointer that is initialized in the writer’s apartment and used from the reader’s apartment. This inconsistency is addressed by the documentation for CoMarshalInterThreadInterfaceInStream, which states that the resultant IStream interface pointer can be accessed from any apartment in the process.