Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Marshaling COM Interfaces


Marshaling COM Interfaces

Last issue, we looked into using Win32 events to notify threads of changes in state within a multithreaded application. In this issue, we’ll continue to look at threading by examining another similar problem: Sharing COM objects in a multithreaded application.

One of the dubious additions to COM was the idea of the apartment-model. I never liked the whole concept of apartments—the use of the Windows message queue to create a serialization of calls to an object instance to ensure that if the object was used in a multithreaded situation, it could be accessed safely (excluding shared or “static” data), and without additional code from the developer. In essence, Microsoft created a technology that attempted to shield the developer from having to worry about threading issues such as concurrent data access and using synchronization objects like critical sections. However, the apartment-model also fooled more than one developer into thinking that COM objects could be used in a multithreaded situation with abandon. Many late nights hunkered over the keyboard with the debugger testify to how wrong that was.

What’s not always understood is that COM concurrency rules stipulate that if one thread wants to use an object created in another thread, the interface to that object must first be marshaled in order to be accessed. Marshaling is the process by which an interface is packaged for use across thread or process boundaries. Although it is possible to create custom marshaling logic, for this issue we’ll assume that we’ll use the standard marshaling provided by Windows.

It turns out that Microsoft provides several ways to safely pass interfaces across thread boundaries. The approach used depends largely upon what makes sense for the design of your system. Let’s look at these approaches.

IStream

This is probably the standard approach to passing interfaces across thread boundaries. The SDK function CoMarshalInterThreadInterfaceInStream is used to marshal an interface to an IStream. If you haven't used IStream's before, think of them as similar in concept to a disk file. This IStream interface can then be passed directly to another thread, where is it unmarshaled back to an interface via a call to CoGetInterfaceAndReleaseStream. This function does the double-duty of recreating the interface pointer and releasing the IStream. Hmmm. What if you want to pass this marshaled interface to multiple threads? Releasing the IStream in a given thread makes that IStream invalid for any other thread that wants to use it. An alternate function you can use in this situation is CoUnmarshalInterface. In fact, CoGetInterfaceAndReleaseStream is really just a wrapper for calls to CoUnmarshalInterface and IStream.Release.

Wait a minute. The reason we have to marshal interfaces across thread boundaries is to respect COM's concurrency rules as mentioned earlier. But then, how can we use an IStream interface across thread boundaries? It turns out that COM treats an IStream as a "special type of interface, which can be safely used across threads. An alternative design that would have been more in line with the rest of Windows would have specified that the marshaling API's return a HANDLE object. So you can think of the IStream as being similar to a HANDLE when marshaling. Although the IStream has methods on it that allow reading and writing the byte stream, you wouldn't typically call these methods for any reason.

Global Interface Table (GIT)

This approach is, in some ways, easier to work with than IStream's, but its downside is that it's only available with NT4 SP3 or Win95 with DCOM 1.2 (or Win98).

The first step is to create an instance of the GIT:

CComPtr<IGlobalInterfaceTable> pGIT;
CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                 NULL,
                 CLSCTX_INPROC_SERVER,
                 IID_IGlobalInterfaceTable,
                 (void **)&pGIT)

With an interface now to the GIT, we can register an interface that we want to pass between threads:

DWORD dwCookie=0;
pGIT->RegisterInterfaceInGlobal( IID_IUnknown,pMyInterface,&dwCookie );

We get back a "cookie value that refers to the interface we passed in the call to RegisterInterfaceInGlobal. Kinda like the HANDLE concept used in other API's (and mentioned earlier).

Now, we can pass the cookie to the other thread via some application mechanism (possibly on startup of the thread). The thread can access this interface via a call to GetInterfaceFromGlobal as in:

CComPtr<IGlobalInterfaceTable> pThreadGIT;
CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                  NULL,
                  CLSCTX_INPROC_SERVER,
                  IID_IGlobalInterfaceTable,
                  (void **)&pThreadGIT)

IUnknown* pUnk;	
pThreadGIT->GetInterfaceFromGlobal(
 dwCookie,IID_IUnknown,(void**)&pUnk );

Although the thread seems to be creating its own instance of the GIT via a call to CoCreateInstance, it is really accessing the same object as the original thread-there is one GIT object per process acting as a singleton.

Later, when the application is finished with the interface in the GIT, it can be released from any thread via a call to RevokeInterfaceFromGlobal. Also, use of the GIT acts similarly to using CoUnmarshalInterface in that the interface can be retrieved multiple times from the GIT.

Running Object Table (ROT)

The running object table is yet another way to share COM interfaces safely between threads. Its special claim to fame over the alternatives is that it's possible to share interfaces between processes, not just threads. This might be especially useful for more complex applications that are composed of multiple executables or applications written by different vendors that want to share the same objects. A file moniker is used to identify the object and register it within the ROT.

To be honest, I've yet to find a reason to use the ROT in my own code. There are so many other ways to share data between EXE's now-from the primitive clipboard up to sophisticated web services-that the ROT is probably not as necessary as it once was. If you are doing a lot of OLE compound document work, then you probably need to take a closer look at the ROT.

One other advantage over the GIT approach is that it's available on Win95 without the DCOM upgrade. This makes it particularly useful in a situation where you need to support the original Win95 platform and can't upgrade it to support DCOM 1.2.

Suggestion: If you find yourself needing to marshal an interface to pass to a single thread and that's it, then use the IStream approach. If you need to pass the same interface to multiple threads, then the GIT approach is better if you don't need to support the original Win95 platform.

Whatever approach you use, you should ensure that you create a proxy/stub DLL for your COM objects and register it on the machines where your application will run. If you're using Visual Studio to create your COM objects, this is done automatically for you in the project wizard. Look for a DLL ending with the letters "ps.dll in the directory where you created your project (note: if you indicated in the project settings that you wanted to "merge proxy/stub code, you won't see the DLL because the proxy/stub code was merged into the project DLL itself).


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C. He can be contacted at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.