Cross-Context Manual Marshaling

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 Global Interface Table

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.

The GIT Wrapper Class

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset