Page #80 (Apartments and Standard Marshaling)


Cross-Context Access

In Chapter 5, I made the case that an interface pointer, either raw or proxy, should always be marshaled when passed from one context to another. I also noted that application programmers rarely deal with marshaling because COM handles the most common cases of interface pointer marshaling implicitly. Explicit marshaling is only needed when a reference to an object is shared outside the scope of an interface method call. COM provides APIs to explicitly marshal and unmarshal interface pointers.

Let s put these marshaling APIs to use.

You may be wondering why I am talking about interface marshaling in a chapter on concurrency management instead of in Chapter 5 which dealt with contexts. Well, it is just convenient to demonstrate marshaling across apartments. After all, cross-apartment access does imply cross-context access.

The SDK provides two APIs, CoMarshalInterface and CoUnmarshalInterface, to marshal and unmarshal an interface pointer, respectively. Following are their prototypes:

 STDAPI CoMarshalInterface(     IStream *pStm,         //Pointer to the stream used for marshaling      REFIID riid,           //Reference to the identifier of the interface      IUnknown *pUnk,        //Pointer to the interface to be marshaled      DWORD dwDestContext,   //Destination context      void *pvDestContext,   //Reserved for future use      DWORD mshlflags        //Reason for marshaling  );  STDAPI CoUnmarshalInterface(     IStream * pStm,       //Pointer to the stream      REFIID riid,          //Reference to the identifier of the interface      void ** ppv           //Address of output variable that receives                            //the interface pointer requested in riid  ); 

The first API, CoMarshalInterface, marshals a context-relative object reference to context-neutral byte streams. These byte streams can be passed to any other contexts.

The second API reads the byte stream and creates a proxy in the caller s context.

Let s write a program to demonstrate the usage of these APIs.

Using the ATL wizard, I will generate a free-threaded component MyTest that supports an interface IMyTest. This interface has a method DoIt whose implementation just returns S_OK.

 STDMETHODIMP CMyTest::DoIt()  {   return S_OK;  } 

The client program has two threads. The main thread enters an MTA and creates an object of class MyTest.

 int WinMain(...)  {   // Enter MTA    ::CoInitializeEx(NULL, COINIT_MULTITHREADED);    // Create an object and get IMyTest interface    IMyTestPtr spTest(__uuidof(MyTest));    ...  } 

The secondary thread runs in an STA. It calls the interface method DoIt on the object that was created by the main thread.

 class CMyThread : public CCPLWinThread  { public:    IMyTestPtr m_spTest;    void Proc()    {     try {       m_spTest->DoIt();      }      catch(_com_error& e) {       ReportError(e);      }    }  }; 

The main thread creates the secondary thread, passes the interface pointer to the secondary thread, starts it, and waits for it to finish.

 CMyThread myThread; myThread.Init();  myThread.SetApartmentToEnter(COINIT_APARTMENTTHREADED);  // Pass the interface pointer to the thread without marshaling  myThread.m_spTest = spTest;  // Start the thread and wait for the thread to quit  myThread.StartThread();  myThread.WaitForCompletion(); 

Note that I haven t used any marshaling APIs yet. Let s run the client program and see what happens.

The call to DoIt in the secondary thread fails with an error, RPC_E_WRONG_THREAD, which stands for the application called an interface that was marshaled for a different thread.

So just passing raw interface pointers between two apartments (therefore, two contexts) doesn t work.


This experiment was designed to bring forth this error. Sometimes, passing an interface pointer from one context to another without explicit marshaling may seem to work. However, there is no guarantee that the Services provided by COM will function correctly.

Now let s modify our code to use the marshaling APIs.

The following code snippet shows how an interface pointer can be marshaled into a global handle:

 HGLOBAL MyMarshalInterface(IMyTestPtr spTest)  {   // Create an IStream pointer on a dynamally allocated global  memory    IStreamPtr spStream;    HRESULT hr = ::CreateStreamOnHGlobal(0, FALSE, &spStream);    VERIFYHR(hr);    // Marshal the interface pointer into the stream    hr = ::CoMarshalInterface(spStream,      __uuidof(IMyTest),      spTest,      MSHCTX_INPROC,      0,      MSHLFLAGS_NORMAL);    VERIFYHR(hr);    // obtain the handle to the global memory and return it    HGLOBAL hRetVal;    hr = ::GetHGlobalFromStream(spStream, &hRetVal);    VERIFYHR(hr);    return hRetVal;  } 

While making a call to CoMarshalInterface, a developer can specify the distance of the destination context as a) within the same process, b) in a different process on the local machine, or c) on a different machine. COM uses this information as a hint for creating an efficiently marshaled packet. For our example, I specified MSHCTX_INPROC to indicate that the importing context is within the same process.

CoMarshalInterface also supports the notion of single-time unmarshaling or multiple-time unmarshaling. If the object reference is marshaled using the MSHLFLAGS_NORMAL flag, it is referred to as normal marshaling or call marshaling. The semantics of normal marshaling imply one-time unmarshaling only. Normal marshaling also has a timeout period within which if the unmarshaling doesn t occur, the data becomes invalid. Under table marshaling, the marshaled information is written to a globally available table, where it can be retrieved by multiple clients and unmarshaled multiple times. And there is no timeout period associated with it.

The case of interface marshaling within a process is so common that the SDK defines a simplified variation of the marshaling APIs, CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream. Using these APIs is straightforward and is left as an exercise for the reader.

Global Interface Table (GIT)

In the preceding code, the object reference was marshaled using normal marshaling semantics. Under normal marshaling, the data becomes invalid after it has been unmarshaled once. It is often desirable to marshal a pointer from one thread and have multiple threads (perhaps from different contexts) unmarshal the interface pointer when needed. Though table marshaling can be used to achieve this, for reasons beyond the scope of this book, table marshaling doesn t work if the interface pointer to be marshaled happens to be a proxy.

To solve the problem of making an interface pointer accessible to multiple contexts, COM introduces a facility called the Global Interface Table (GIT).

The GIT is a process-wide table that contains marshaled interface pointers that can be unmarshaled multiple times. This is semantically equivalent to table marshaling, except that it works for raw references as well as proxies.

The GIT supports an interface IGlobalInterfaceTable that has methods to add, retrieve, or remove one or more interface pointers. When an interface pointer is added to the GIT, it returns a DWORD cookie (called the GIT cookie). If this cookie is published to other contexts by some means, each interested context can retrieve the interface pointer using this cookie as a key.

The following code snippet shows how the ITest interface pointer from our example can be added to the GIT:

 DWORD MyMarshalInterfaceInGIT(IMyTestPtr spTest)  {   // Create an instance of GIT    IGlobalInterfaceTablePtr spGIT;    HRESULT hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,      0,      CLSCTX_INPROC_SERVER,      __uuidof(IGlobalInterfaceTable),      (void**) &spGIT);    // Add our interface pointer to GIT    DWORD dwCookie;    hr = spGIT->RegisterInterfaceInGlobal(spTest,      __uuidof(IMyTest), &dwCookie);    return dwCookie;  } 

COM provides a standard implementation of the GIT as an in-proc component with CLSID, CLSID_StdGlobalInterfaceTable. The implementation is completely thread-safe.

The code above obtains the IGlobalInterfaceTable interface pointer from the standard implementation, registers the IMyTest interface pointer with the GIT, and returns the generated cookie.

Any other context within the process can use the generated cookie to unmarshal the interface pointer and get a proxy, as shown in the following code snippet:

 IMyTestPtr MyUnmarshalInterfaceFromGIT(DWORD dwCookie)  {   // Create an instance of GIT    IGlobalInterfaceTablePtr spGIT;    HRESULT hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,      0,      CLSCTX_INPROC_SERVER,      __uuidof(IGlobalInterfaceTable),      (void**) &spGIT);    IMyTestPtr spTest;    hr = spGIT->GetInterfaceFromGlobal(dwCookie,      __uuidof(IMyTest), (void**) &spTest);    return spTest;  } 

The above example created an instance of the GIT on an as-needed basis. In typical applications, a single process-wide instance is created just once at the startup.


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
Year: 2000
Pages: 129 © 2008-2017.
If you may any questions please contact us: