Cross-context call interception via marshaling is how COM+ provides its component services to your object. A client in a different context cannot access your object directly, even if it has a direct raw pointer to it. Intercepting the call and performing the right service switches requires a proxy and a stub in between. Otherwise, the object executes in the client context, possibly in an ill-suited runtime environment. If the client gets the pointer to your object in one of the following ways:
CoCreating the object
Querying an object the client already has for additional interfaces
Receiving the pointer as a method parameter on a COM interface
Then COM+ will, under the hood, put interceptors (proxys and stubs) in place, to make sure all calls into the object are marshaled. If the client does anything else to obtain the interface pointer, such as retrieve it from a global variable or a static member variable shared among all clients, you have to marshal the pointer manually yourself. Dealing with pooled objects is another situation requiring manual marshaling, as you will see in the next chapter.
Classic COM requires that all cross-apartment calls be marshaled, even when the call is in the same process, to ensure threading model compatibility. The classic COM mechanisms for manually marshaling interface pointers across apartment boundaries have been made context-aware. They are what you should use to marshal interface pointers manually across context boundaries with COM+.
Generally, these mechanisms rely on the CoMarshalInterface( )
and CoUnmarshalInterface( )
functions. When you need to manually marshal an interface pointer
from Context A to Context B, you would serialize the interface
pointer into a stream in Context A using CoMarshalInterface( )
, and get it out of the stream using
CoUnmarshalInterface( )
in Context B. This
sequence would manually set up proxies in Context B for accessing the
object. You can also use the
CoMarshalInterThreadInterfaceInStream( )
and CoGetInterfaceAndReleaseStream( )
helper methods to automate some of the steps required when using just
CoMarshalInterface( )
and
CoUnmarshalInterface( )
.
The preferred way to manually marshal interface pointers between contexts is by using the global interface table (GIT). Every process has one globally accessible table used for manually marshaling interface pointers. Globally accessible means accessible from every context and every apartment in the process. An interface pointer is checked into the GIT in one context. Then you get back an identifying cookie (a number), which is context-neutral and can be passed freely between clients across context boundaries, placed in global variable or class members, etc. Any client, at any context in the process, can access the GIT and use the cookie to get a properly marshaled interface pointer for its context. The GIT is only useful in cross-context marshaling in the same process and has no role in cross-process marshaling.
The GIT saves you the agony of programming directly against
CoMarshalInterface( )
or its helper
functions, and more importantly, it overcomes a serious limitation of
the CoMarshalInterface( )
function. Using
CoMarshalInterface( )
, you can unmarshal an
interface pointer just once for every CoMarshalInterface( )
call. Using the GIT, you can check an interface pointer
into the GIT once and check out interface pointers multiple times.
The GIT supports the
IGlobalInterfaceTable
interface, which is defined
as:
interface IGlobalInterfaceTable : IUnknown { HRESULT RegisterInterfaceInGlobal([in]IUnknown *pUnk, [in]REFIID riid, [out]DWORD *pdwCookie); HRESULT RevokeInterfaceFromGlobal([in]DWORD dwCookie); HRESULT GetInterfaceFromGlobal([in]DWORD dwCookie, [in]REFIID riid, [out]void** ppInterface); }
You can create the GIT with the class ID of
CLSID_StdGlobalInterfaceTable
.
RegisterInterfaceInGlobal( )
is used to check an interface pointer into the GIT from within one
context and to get back the identifying cookie.
GetInterfaceFromGlobal( )
is used to get a properly marshaled interface pointer at any other
context using the cookie. RevokeInterfaceFromGlobal( )
is used to remove the interface pointer from the GIT. Example 2-2 shows how to use the
IGlobalInterfaceTable
interface to manually
marshal a pointer of type IMyInterface
from
Context A to Context B, or any other context in the process, using
the GIT and a global variable.
Example 2-2. Manually marshaling a pointer using the GIT
//In context A: HRESULT hres = S_OK; extern DWORD dwCookie;//A global variable accessible in any context IMyInterface* pMyInterface = NULL; /* Some code to initialize pMyInterface, by creating an object that supports it*/ //Now, you want to make this object accessible from other contexts. dwCookie = 0; //Create the GIT IGlobalInterfaceTable* pGlobalInterfaceTable = NULL; hres = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,NULL, CLSCTX_INPROC_SERVER,IID_IGlobalInterfaceTable, (void**)&pGlobalInterfaceTable); //Register the interface in the GIT hres = pGlobalInterfaceTable ->RegisterInterfaceInGlobal
(pMyInterface, IID_IMyInterface, &dwCookie); pGlobalInterfaceTable->Release( );//Don't need the GIT ///////////////////////////////////////////////////////////////////////////////// //In context B: IMyInterface* pMyInterface = NULL; IGlobalInterfaceTable* pGlobalInterfaceTable = NULL; hres = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,NULL, CLSCTX_INPROC_SERVER,IID_IGlobalInterfaceTable, (void**)&pGlobalInterfaceTable); //Get the interface from the GIT hres = pGlobalInterfaceTable->GetInterfaceFromGlobal
(dwCookie, IID_IGlobalInterfaceTable, (void**)&pMyInterface); pGlobalInterfaceTable->Release( ); /* code that uses pMyInterface */ pMyInterface->Release
( ); ////////////////////////////////////////////////////////////////////////////////// //Don't forget to revoke from the GIT when you are done or before shutting down IGlobalInterfaceTable* pGlobalInterfaceTable = NULL; //You can use a cached pointer to the GIT or re-create it: hres = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,NULL, CLSCTX_INPROC_SERVER,IID_IGlobalInterfaceTable, (void**)&pGlobalInterfaceTable); hres = pGlobalInterfaceTable->RevokeInterfaceFromGlobal
(dwCookie); pGlobalInterfaceTable->Release( );
The GIT increments the reference count of the interface pointer when
it is registered. As a result, the client that registered the
interface pointer can actually let go of its own copy of the
interface pointer, and the object would not be destroyed. When you
revoke the object from the GIT, the GIT releases its copy. When the
process shuts down gracefully, if you forget to revoke your
interfaces, the GIT revokes all the objects it still has, allowing
them to be released. The GIT will AddRef( )
an
interface pointer that is returned from a call to
GetInterfaceFromGlobal( )
. A client should call a
matching Release( )
for every
GetInterfaceFromGlobal( )
called. Any client in
the process can revoke a registered interface pointer. However, I
recommend as a convention that the client who registered the object
should be the one revoking it.
Using the
raw global interface table has a few drawbacks. The resulting code is
somewhat cumbersome and the IGlobalInterfaceTable
method names are too long. In addition, the methods are not type safe
because they require you to cast to and from a
void*
pointer. Previously, I saw a need for
writing a simple C++ wrapper class that compensates for the raw usage
drawbacks. The wrapper class provides better method names and type
safety, and because the class ID for the GIT is standard, its
constructor creates the global interface table and its destructor
releases it.
The wrapper class is called
CGlobalInterfaceTable
and is defined as:
template <class Itf,const IID* piid> class CGlobalInterfaceTable { public: CGlobalInterfaceTable( ); ~CGlobalInterfaceTable( ); HRESULT Register(Itf* pInterface,DWORD *pdwCookie); HRESULT Revoke(DWORD dwCookie); HRESULT GetInterface(DWORD dwCookie,Itf** ppInterface); protected: IGlobalInterfaceTable* m_pGlobalInterfaceTable; private://prevent misuse CGlobalInterfaceTable(const CGlobalInterfaceTable&); void operator =(const CGlobalInterfaceTable&); };
By defining the GIT helper macro:
#define GIT(Itf) CGlobalInterfaceTable<Itf,&IID_##Itf>
You get automatic type safety because the compiler enforces the match between the interface ID and the interface pointer used.
Using the wrapper class is trivial. Here is the code required to retrieve an interface pointer from the table, for example:
IMyInterface* pMyInterface = NULL; GIT(IMyInterface) git; git.GetInterface(dwCookie,&pMyInterface);
Compare this code to Example 2-2. Using the wrapper class results in concise, elegant, and type-safe code. The GIT wrapper class is included as part of the source code available with this book.