5,445,109 members and growing! (13,692 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » COM / COM+ » Beginners     Beginner

Understanding The COM Single-Threaded Apartment Part 2

By Lim Bio Liong

Learn the fundamental principles of the COM Single-Threaded Apartment Model by code examples.
VC6, VB 6, C++, VBWindows, Win2K, WinXP, ATL, COM, VS6, Visual Studio, Dev

Posted: 6 Feb 2005
Updated: 18 Feb 2005
Views: 80,740
Bookmarked: 78 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
42 votes for this Article.
Popularity: 7.67 Rating: 4.73 out of 5
1 vote, 2.4%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 4.8%
4
39 votes, 92.9%
5

Introduction

This article is a direct follow-up to my previous article entitled "Understanding The COM Single-Threaded Apartment Part 1". In part one, we concentrated on building a strong foundation on the knowledge and understanding of the general architecture of the COM Single-Threaded Apartment Model. We explored the basic concepts behind STAs. We studied the STA in action by analysing the inner workings of several test objects and clients. In particular, we observed some simple inter-apartment method calls which have been pre-arranged by COM. We also noted how marshaling operations are performed transparently without our knowledge.

Here in part two, we will further solidify the foundations built up in part one by looking at more sophisticated examples. The central theme behind part two is: interface pointer marshaling. We shall show how to perform explicit marshaling of COM interface pointers from one apartment to another, using basic and advanced examples. Related to the subject of marshaling is the technique behind how the events of an object can be fired from an external thread (usually a thread of a foreign apartment). We will demonstrate this rigorously in the section "Demonstrating Advanced STA".

As promised in part one, we will also provide a proper documentation for the cool helper function ThreadMsgWaitForSingleObject() which we used previously in the example codes of part one.

Synopsis

Listed below are the main sections of this article together with general outlines of each of their contents:

COM Interface Marshaling

This section provides a general introduction to the concept of COM Interface Marshaling. The semantics of various terms associated with marshaling are established. Note that we do not need to use precise, normative definitions. The goal of this section is to provide sufficient background information in order to help the reader eventually gain a good working knowledge and understanding of these terms. We will also briefly gloss over the various techniques of marshaling.

Demonstrating Inter-Apartment Interface Marshaling

This section begins a thorough study of the three techniques of explicit marshaling. A simple COM object and a single C++ test program (serving as the client of the COM object) are used in this study. Each marshaling technique is demonstrated through a distinct entry-point function in the test program.

We will also show what can happen when we do not use appropriate marshaling methods to transfer an interface pointer from one thread to another. For this particular "negative" demonstration, we will also use an additional COM object which was written in Visual Basic.

Demonstrating Advanced STA

This section presents an advanced example of a COM application which includes the use of an STA object. The crucial point of this example is the demonstration of the object's ability to fire its event to the application from an external thread. We will also introduce, in this section, our C++ class CComThread which will be used to great effect in the demonstration program. Halfway through this section, we will divert a little in order to talk about ATL-generated Connection Point Proxies. This small side-note will serve to explain how an ATL COM Object can access and use the event sinks of its client.

The ThreadMsgWaitForSingleObject() Function

A proper documentation for this cool utility function (first introduced in Part 1) is presented in this section.

Without further ado then, let us begin our exploration into the world of Inter-Apartment Marshaling.

COM Interface Marshaling

Background On Marshaling

In part one, we noted that the purpose of having COM apartments is to ensure the thread-safety of COM objects. We learned that an apartment is a logical container inside an application for COM objects which share the same thread access rules (i.e., regulations governing how the methods and properties of an object are invoked from threads within and without the apartment in which the object belongs) and that all COM objects live inside exactly one apartment.

In many situations, objects need to be accessed from multiple apartments. In order that an object be used in an apartment other than its own, the object's interfaces must first be exported from its original apartment and imported into the target apartment. Note that I highlighted the word "interfaces", for it is actually an object's interfaces that are exported/imported and not the object itself. Note also that an interface is exported/imported from/to apartments, not threads.

When COM performs the exporting and importing of interface pointers, it does so using a conjugate pair of procedures known as marshaling and unmarshaling. This is the collective act of transforming an interface pointer from a source apartment into a series of bytes and then transporting this series of bytes to a target apartment which will reverse-transform the bytes back into an interface pointer usable by the target apartment.

The transformation of anything into a series of bytes is known as serialization. The serialization of an interface pointer is better known as marshaling the interface pointer.

The series of bytes obtained from serialization is more commonly referred to as a stream. The stream obtained from marshaling is also known as a marshaled data packet. The contents of a marshaled data packet uniquely identify the underlying object and its owning apartment. It also contains data that can be used to import an interface of the object into any apartment. This destination apartment can reside within the same application, across processes in a machine, and even across machines. This stream is always referenced by a pointer to an IStream interface that represents a stream object (which is a container for bytes).

The process of marshaling an interface pointer inside an STA into a stream object is illustrated by the diagram below:

Interface Marshalling

The reverse-transformation of a series of bytes back into its original form is known as deserialization. The deserialization of a stream of bytes (containing a marshaled data packet) back into an interface pointer is referred to as unmarshaling an interface pointer.

The process of transporting a stream from one apartment to another can be achieved by any means appropriate to an application. It all depends on the nature of the stream object behind the IStream interface pointer. Remember that the marshaled data packet contained within the stream object is meant to be neutral to any apartment. It is not an interface pointer until it gets unmarshaled. However, the stream object itself is a COM object which must belong to an apartment. To get to the marshaled data packet contained within its buffer, we must use the IStream interface methods of the stream object.

If we use OLE's implementation of stream objects, we can assume that it will be thread-safe and that its IStream interface pointer may be accessed freely across apartments without the need for its own marshaling. In this case, the IStream interface pointer can be global in an application and can be freely passed from one function to another and from one thread to another. We shall be using OLE's stream objects throughout this article.

An unmarshaled interface pointer is also known as a proxy. It is an indirect pointer to the same interface of the original object. This proxy can be legally accessed by any thread in the importing apartment. When a method of the interface is called inside a thread of the foreign apartment, it is the proxy's responsibility to transfer control back to the object's own apartment and ensuring that the method invocation is executed inside this original apartment. This way, thread access rules are maintained in harmony across apartments. Hence, we can say that marshaling is the means by which thread access rules are maintained across apartments.

The process of unmarshaling an interface pointer inside a stream object into a proxy usable inside an importing STA is illustrated by the diagram below:

Marshaling Techniques

There are two general types of marshaling: implicit and explicit.

Implicit marshaling refers to marshaling which is performed by COM automatically. The following are situations in which this occurs:

  1. When an object is instantiated inside an incompatible apartment (i.e., the apartment's model is different from that of the object).
  2. When an object, served inside a COM EXE server (local or remote), is instantiated inside a client application.
  3. When a proxy's methods are called, any interface pointers that are passed as parameters will involve automatic marshaling by COM.

We have already witnessed implicit marshaling as described in points 1 and 2, back in part one. Recall that in the sample programs of part one, the marshaling procedures used to facilitate cross-apartment method calls were all performed by COM. They were setup transparently without our knowledge. The marshaling works were put in place and set in motion during creation time (as a result of COM detecting incompatibility between the apartment models of an object and its creating thread).

Explicit marshaling refers to marshaling which needs to be specifically coded by the developer. There are three ways to perform explicit interface pointer marshaling:

  1. Using the low-level CoMarshalInterface() and CoUnmarshalInterface() APIs.
  2. Using the higher-level CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream() APIs.
  3. Using the high-level Global Interface Table (GIT).

In addition to the ways and means of performing it, there are two categories of explicit marshaling:

  1. Normal (or one-time) Marshaling.
  2. Table Marshaling.

In the next section, we will explore each of the three techniques of marshaling/unmarshaling and expound on normal and table marshaling along the way. Just like in part one, I will endeavour to use example codes to effectively illustrate our points. Standard Single-Threaded Apartments are used throughout our sample codes.

Demonstrating Inter-Apartment Interface Marshaling

In this section, we will demonstrate the coding techniques used to achieve each of the three above-mentioned explicit marshaling methods. We will show the effectiveness of our marshaling work not only by showing successful completion of method calls from what we profess to be proxies but also by evaluating the ID of the thread which is executing when a COM object's method is invoked via a proxy. For a standard STA object, this ID must match that of its own STA thread (i.e., the thread in which the object was instantiated). It must be different from the ID of the thread which contains the proxy. This simple basic principle is used throughout the examples in this section.

Additionally, we will also show the deadly effects of passing an STA interface pointer directly without any marshaling.

The source code used throughout this example can be found in the "Test Programs\VCTests\DemonstrateSTAInterThreadMarshalling\VCTest01" folder of the source codes ZIP file which accompanies this article. This folder contains a single console application program which uses a simple example STA COM object (of coclass SimpleCOMObject2 and which implements the interface ISimpleCOMObject2). This STA COM object was used in many examples in part one and the code for this object is located in the "SimpleCOMObject2" folder in the source ZIP file.

The ISimpleCOMObject2 interface includes just one method: TestMethod1(). TestMethod1() is very simple. It displays a message box which shows the ID of the thread in which the method is running on:

STDMETHODIMP CSimpleCOMObject2::TestMethod1()
{
 TCHAR szMessage[256];
 sprintf (szMessage, "Thread ID : 0x%X", GetCurrentThreadId());
 ::MessageBox(NULL, szMessage, "TestMethod1()", MB_OK);
 return S_OK;
}

The "VCTest01" console program consists of a main() function...:

/* This program aims to demonstrate the transfer of an interface pointer
   across apartments. There are 4 methods shown :
   1. By way of IStream using low-level APIs.
   2. By way of IStream using high-level APIs.
   3. By way of the Global Interface Table (GIT).
   4. By an incorrect and dangerous way.
*/
int main()
{
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* Display the current thread id.*/
  /* Let's say this is thread_id_1.*/
  DisplayCurrentThreadId();
  if (1)
  {
    ISimpleCOMObject2Ptr spISimpleCOMObject2;
    /* Instantiate coclass SimpleCOMObject2     */
    /* and get its ISimpleCOMObject2 interface. */
    spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2));
    /* Call its TestMethod1() method. Note the thread id. */
    /* This should be thread_id_1.                        */
    spISimpleCOMObject2 -> TestMethod1();
    DemonstrateInterThreadMarshallingUsingLowLevelAPI(spISimpleCOMObject2);
    DemonstrateInterThreadMarshallingUsingIStream(spISimpleCOMObject2);
    DemonstrateInterThreadMarshallingUsingGIT(spISimpleCOMObject2);
    DemonstrateDangerousTransferOfInterfacePointers(spISimpleCOMObject2);
    /* Call its TestMethod1() method again.          */
    /* The thread id displayed is still thread_id_1. */
    spISimpleCOMObject2 -> TestMethod1();
  }
  ::CoUninitialize();
  return 0;
}

... and four global functions each of which serves as an entry point for demonstrating an aspect of marshaling:

void DemonstrateInterThreadMarshallingUsingLowLevelAPI
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
);
void DemonstrateInterThreadMarshallingUsingIStream
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
);
void DemonstrateInterThreadMarshallingUsingGIT
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
);
void DemonstrateDangerousTransferOfInterfacePointers
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
);

Let us study the execution steps of main():

  1. The main() function enters an STA early on.
  2. It then uses the DisplayCurrentThreadId() function (first introduced in part one) to display the ID of its thread. Let's say this is thread_d_1.
  3. It then instantiates coclass SimpleCOMObject2 (referenced by the smart pointer spISimpleCOMObject2).
  4. spISimpleCOMObject2 and main()'s thread are thus in the same apartment.
  5. The main() function then invokes spISimpleCOMObject2's TestMethod1() method. The ID of the thread which executes TestMethod1() is displayed. You will note that this is thread_id_1. This is consistent with point 4 above.
  6. The main() function next uses the four demonstration functions one after another to demonstrate marshaling.

Each of the sections that follow will expound on each demonstration function.

Demonstrating The Low-Level CoMarshalInterface() And CoUnmarshalInterface() APIs.

This demonstration uses low-level primitive functions provided by COM to achieve marshaling and unmarshaling. Later demonstrations use higher-level COM APIs. I wanted to start out with low-level functions in order to clearly illuminate the fundamental principles as described previously in the section "Background On Marshaling". I am confident that once the reader understands the primitive functions and basic principles, the later demonstrations will be much easier to comprehend.

Please refer to the code listings of the DemonstrateInterThreadMarshallingUsingLowLevelAPI() function and the ThreadFunc_MarshalUsingLowLevelAPI() function:

/* This function demonstrates a proper way of marshalling an interface    */
/* pointer from one apartment to another.                                 */
/*                                                                        */
/* The method used here involves a stream of bytes that stores the thread-*/
/* independent serialized bytes of an interface pointer. This stream of   */
/* bytes (headed by an IStream interface pointer) is then passed to a     */
/* destination thread of an apartment distinct from the original interface*/
/* pointer's own apartment.                                               */
/*                                                                        */
/* The destination thread then deserializes the bytes into a proxy to the */
/* original interface pointer.                                            */
/*                                                                        */
/* This demonstration uses low-level APIs to achieve its objectives as    */
/* coded in the functions LowLevelInProcMarshalInterface() and            */
/* LowLevelInProcUnmarshalInterface().                                    */
/*                                                                        */
void DemonstrateInterThreadMarshallingUsingLowLevelAPI
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
)
{
  HANDLE  hThread = NULL;
  DWORD   dwThreadId = 0;
  IStream*  pIStream = NULL;
  /* Prepare the serialization bytes of spISimpleCOMObject2   */
  /* and store it inside a stream object handled by pIStream. */
  pIStream = LowLevelInProcMarshalInterface<ISimpleCOMObject2>
    (spISimpleCOMObject2, __uuidof(spISimpleCOMObject2));
  if (pIStream)
  {
    /* Demonstrate the use of a stream of bytes to marshal an interface */
    /* pointer from one thread to another. */
    hThread = CreateThread
    (
      (LPSECURITY_ATTRIBUTES)NULL,
      (SIZE_T)0,
      (LPTHREAD_START_ROUTINE)ThreadFunc_MarshalUsingLowLevelAPI,
      (LPVOID)pIStream,
      (DWORD)0,
      (LPDWORD)&dwThreadId
    );
    ThreadMsgWaitForSingleObject(hThread, INFINITE);
    CloseHandle (hThread);
    hThread = NULL;
    /* Note : do not call pIStream -> Release().   */
    /* This is done in the receiving thread when   */
    /* it calls LowLevelInProcUnmarshalInterface().*/
  }
}
/* This thread function obtains a pointer to   */
/* an ISimpleCOMObject2 interface via a stream */
/* object which contains apartment-independent */
/* serialized bytes of the interface pointer.  */
/*                                             */
/* This set of bytes can be de-serialized into */
/* a proxy to the interface pointer.           */
/* This de-serialization (or unmarshalling)    */
/* process is performed by our own             */
/* LowLevelInProcUnmarshalInterface() function.*/
/*                                             */
DWORD WINAPI ThreadFunc_MarshalUsingLowLevelAPI
(
  LPVOID lpvParameter
)
{
  /* The IStream object may be passed from one thread */
  /* to another. It is thread-independent.            */
  LPSTREAM    pIStream = (LPSTREAM)lpvParameter;
  ISimpleCOMObject2* pISimpleCOMObject2 = NULL;
  /* This thread enters an STA.*/
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* Note the id of this thread.   */
  /* Let's say this is thread_id_2.*/
  DisplayCurrentThreadId();
  /* Deserialize the byte contents of the IStream object */
  /* into an actual interface pointer to be used only    */
  /* within this thread.                                 */
  /*                                                     */
  /* The interface pointer will not be a direct pointer. */
  /* It will be a proxy to the original pointer.         */
  if (pIStream)
  {
    /* pIStream will be Release()'d inside */
    /* LowLevelInProcUnmarshalInterface(). */
    LowLevelInProcUnmarshalInterface<ISimpleCOMObject2>
      (pIStream, __uuidof(ISimpleCOMObject2Ptr), 
       &pISimpleCOMObject2);
  }
  if (pISimpleCOMObject2)
  {
    /* Call the TestMethod1() using the proxy.    */
    /* You will note that the thread id will      */
    /* not be thread_id_2. It will be thread_id_1 */
    /* (main()'s thread id) which is the id       */
    /* of the STA thread in which the object      */
    /* was originally created.                    */
    pISimpleCOMObject2 -> TestMethod1();
    /* You may be surprised that the return value   */ 
    /* of Release() is actually zero, showing that  */
    /* it is the proxy (not the original interface) */
    /* that is Release()'d.                         */
    pISimpleCOMObject2 -> Release();
    pISimpleCOMObject2 = NULL;
  }
  ::CoUninitialize();
  return 0;
}

The general synopsis of DemonstrateInterThreadMarshallingUsingLowLevelAPI() and ThreadFunc_MarshalUsingLowLevelAPI() is listed below:

  1. DemonstrateInterThreadMarshallingUsingLowLevelAPI() is to take main()'s spISimpleCOMObject2 and marshal it into a stream of bytes containing the marshaled data packet of spISimpleCOMObject2's ISimpleCOMObject2 interface.
  2. It will then start a thread (headed by ThreadFunc_MarshalUsingLowLevelAPI()) which takes this stream as parameter.
  3. It will then take a back seat, hand over control to ThreadFunc_MarshalUsingLowLevelAPI(), and wait for the thread to complete.
  4. ThreadFunc_MarshalUsingLowLevelAPI() is designed to be an STA thread which unmarshals the stream passed to it from DemonstrateInterThreadMarshallingUsingLowLevelAPI().
  5. The unmarshaled stream becomes a proxy for the main() function's spISimpleCOMObject2.
  6. We will then demonstrate the effectiveness of the proxy.

Let us now thoroughly go through these two functions:

  1. DemonstrateInterThreadMarshallingUsingLowLevelAPI() will take as parameter a reference to an ISimpleCOMObject2Ptr object.
  2. We know that main() will invoke DemonstrateInterThreadMarshallingUsingLowLevelAPI() and pass its "spISimpleCOMObject2" as parameter. We will assume this fact from here onwards and treat the spISimpleCOMObject2 parameter as equivalent to main()'s spISimpleCOMObject2.
  3. DemonstrateInterThreadMarshallingUsingLowLevelAPI() will use the LowLevelInProcMarshalInterface() function to serialize spISimpleCOMObject2 into a marshaled data packet contained inside a stream object which is represented by an IStream pointer ("pIStream"). We shall discuss LowLevelInProcMarshalInterface() and its counterpart LowLevelInProcUnmarshalInterface() later on.
  4. A thread headed by ThreadFunc_MarshalUsingLowLevelAPI() is then started. The IStream pointer "pIStream" is passed to this thread function as a parameter.
  5. DemonstrateInterThreadMarshallingUsingLowLevelAPI() then sits idle while waiting for the newly started thread to complete. The function ThreadMsgWaitForSingleObject() is used to perform the waiting. We have first met ThreadMsgWaitForSingleObject() in part one. This cool utility will be documented fully later on in this article.
  6. Based on the assumption in point 2, we can say then that the ISimpleCOMObject2 interface pointer of main()'s spISimpleCOMObject2 has been marshaled to ThreadFunc_MarshalUsingLowLevelAPI().
  7. ThreadFunc_MarshalUsingLowLevelAPI() starts up by entering an STA. By this time, it has already converted its parameter into an LPSTREAM ("pIStream").
  8. It then displays its thread ID. Let's say this ID is thread_id_2.
  9. The thread function then calls the LowLevelInProcUnmarshalInterface() function to convert "pIStream" into a pointer to interface ISimpleCOMObject2 (i.e., "pISimpleCOMObject2").
  10. pISimpleCOMObject2 is actually a proxy to the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2. If you compare the memory address of pISimpleCOMObject2 and that of spISimpleCOMObject2's raw interface pointer, you will note that they are different.
  11. We then invoke TestMethod1() on pISimpleCOMObject2. The ID of the thread that executes this method will be displayed. You will note that this ID is not thread_id_2. It will be thread_id_1. That is, it is the ID of main()'s thread.
  12. This is not surprising because main()'s thread is spISimpleCOMObject2's STA thread.
  13. We have thus shown the effectiveness of the interface pointer proxy (in its ability to successfully invoke a method) produced out of our marshaling and unmarshaling procedures.
  14. We have also shown that the proxy has fulfilled its responsibility to pass execution control to its original pointer's apartment.

The low-level inter-apartment marshaling interaction can be illustrated in the diagram below:

An Observation On The Proxy Object

An important observation that I hope the reader noticed is the fact that when Release() is called on pISimpleCOMObject2 (in ThreadFunc_MarshalUsingLowLevelAPI()), the return value is actually zero.

[The return value of C/C++ functions which return integral values (e.g., int, long, BOOL) is always set in the EAX register. By observing the EAX register after Release() is called, we can tell what is the current reference count of the object behind an interface pointer.]

The fact that when the reference count of the object behind pISimpleCOMObject2 is zero implies that this object (the proxy) actually maintains a different reference count separate from that of the original object itself. That the reference count of the proxy has dropped to zero is not surprising since it is used only within ThreadFunc_MarshalUsingLowLevelAPI(). We can assume that the proxy is now no longer accessible and that it will be destroyed sometime from here onwards.

In fact, take note that the proxy is an actual object by itself that is semantically equivalent to the object it represents (in another apartment). A proxy will expose the exact same set of interfaces as the object it represents. If we successfully perform a QueryInterface() call on a proxy, its reference count increases.

LowLevelInProcMarshalInterface() And LowLevelInProcUnmarshalInterface()

The bulk of the work behind DemonstrateInterThreadMarshallingUsingLowLevelAPI() and ThreadFunc_MarshalUsingLowLevelAPI() is actually done by the utility functions LowLevelInProcMarshalInterface() and LowLevelInProcUnmarshalInterface(). These functions are enhanced versions of Don Box's re-creation of the CoMarshalInterThreadInteraceInStream() and CoGetInterfaceAndReleaseStream() COM APIs which are expounded in his great book "Essential COM", Chapter 5.

Let us study these two helper functions one at a time:

/* LowLevelInProcMarshalInterface() uses low-level APIs       */
/* CreateStreamOnHGlobal(), CoMarshalInterface(), and IStream */
/* methods to perform interface pointer marshalling across    */
/* apartments in a process.                                   */
/*                                                            */
template <typename T>
LPSTREAM LowLevelInProcMarshalInterface(T* pInterface, REFIID riid)
{
  IUnknown*  pIUnknown = NULL;
  IStream*  pIStreamRet = NULL;
  /* QI the original interface pointer for its IUnknown interface. */
  pInterface -> QueryInterface (IID_IUnknown, (void**)&pIUnknown);
  /* Once we get the IUnknown pointer, we serialize it into */
  /* a stream of bytes.                                     */
  if (pIUnknown)
  {
    /* Create a Stream Object which will reside in global memory.   */
    /* We set the first parameter to NULL, hence indicating that    */
    /* we want CreateStreamOnHGlobal() to internally allocate       */
    /* a new shared memory block of size zero.                      */
    /* The second parameter is set to TRUE so that when the returned*/
    /* stream object is Release()'d, the global memory will also be */
    /* freed.                                                       */
    ::CreateStreamOnHGlobal
    (
      0,
      TRUE,
      &pIStreamRet
    );
    if (pIStreamRet)
    {
      LARGE_INTEGER li = { 0 };
      /* We use the new stream object to store the marshalling data */
      /* of spISimpleCOMObject2.                                    */
      /* The use of MSHCTX_INPROC indicates that the unmarshaling   */
      /* of the data in the stream will be done in another apartment*/
      /* in the same process.                                       */
      ::CoMarshalInterface
      (
        pIStreamRet,
        riid,
        (IUnknown*)pIUnknown,
        MSHCTX_INPROC,
        NULL,
        MSHLFLAGS_NORMAL
      );
      /* Always reset the stream to the beginning.*/
      pIStreamRet -> Seek(li, STREAM_SEEK_SET, NULL);
    }
    pIUnknown -> Release();
    pIUnknown = NULL;
  }
  return pIStreamRet;
}

LowLevelInProcMarshalInterface() is designed to serialize an interface pointer into a series of bytes stored inside an OLE stream object. It is also a templated function that takes an interface type as its template parameter. It is thus designed to be specialized for various COM interfaces. It takes as parameters a pointer to the interface (to be marshaled) as well as the IID of this interface, and returns a pointer to the IStream interface of an OLE stream object which contains the marshaled data packet of the input interface pointer.

Let us step through this function carefully:

  1. LowLevelInProcMarshalInterface() first performs a QueryInterface() on the input interface pointer. This is done to obtain its IUnknown interface. LowLevelInProcMarshalInterface() uses the low-level CoMarshalInterface() Win32 API to perform the marshaling operation and CoMarshalInterface() requires an IUnknown pointer for input.
  2. LowLevelInProcMarshalInterface() then uses the CreateStreamOnHGlobal() API to create a stream object which will reside in the global memory. In calling CreateStreamOnHGlobal(), we set the first parameter to NULL to indicate that we want the API to internally allocate a new memory block of size zero. The second parameter is set to TRUE so that when the returned IStream interface pointer of the stream object is Release()'d, the global memory will also be freed.
  3. As has been discussed previously, the purpose of creating a stream object is to store the serialized bytes (a.k.a. marshaled data packet) of an interface pointer which can be used for later importing into an apartment. One advantage of using IStream is that IStream implementations are bound by specifications to automatically increase the size of their buffers dynamically as and when required.
  4. Next, we call upon the CoMarshalInterface() API to take the IUnknown interface of the input interface pointer (to be marshaled) and serialize it into a string of bytes that contain the data necessary for importing the interface pointer to any apartment of the application. The use of MSHCTX_INPROC as the fourth parameter indicates that the marshaled data packet is meant to be imported into an apartment residing in the same application. That is, the unmarshaling of the data in the stream will be done in another apartment in the same process.
  5. The use of the MSHLFLAGS_NORMAL as the last parameter indicates that the marshaled data packet contained in the stream object can be unmarshaled just once, or not at all. If the receiving apartment successfully unmarshals the data packet, the data packet is automatically destroyed (via an API named CoReleaseMarshalData()) as part of the unmarshaling process. If the receiver fails to unmarshal the stream, the stream must be destroyed nevertheless (via CoReleaseMarshalData()) to prevent memory leakage.
  6. Finally, it is important that the stream position pointer be reset to its beginning. We do this using IStream::Seek(). This must at least be done before unmarshaling is attempted otherwise the unmarshaling function (could be LowLevelInProcUnmarshalInterface() or could be another function) may use the bytes of the stream object starting from wherever the position pointer points to at the time. This may result in unmarshaling failure.

Point 5 refers to a type of marshaling mentioned previously as "Normal" or "One-Time" marshaling. This is so-called because we expect the marshaled data packet to be used just once and then be destroyed. Reuse of the data packet is not possible in this case.

Let us now study LowLevelInProcUnmarshalInterface():

/* LowLevelInProcUnmarshalInterface() uses low-level APIs     */
/* CoUnmarshalInterface(), CoReleaseMarshalData(), and        */
/* IStream methods to perform interface pointer unmarshalling */
/* for an apartment in a process.                             */
/*                                                            */
template <typename T>
void LowLevelInProcUnmarshalInterface
(
  LPSTREAM pIStream, 
  REFIID riid, 
  T** ppInterfaceReceiver
)
{
  /* Deserialize the byte contents of the IStream object */
  /* into an actual interface pointer to be used only    */
  /* within this thread.                                 */
  /*                                                     */
  /* The interface pointer will not be a direct pointer. */
  /* It will be a proxy to the original pointer.         */
  if (pIStream)
  {
    LARGE_INTEGER li = { 0 };
    /* Make sure stream pointer is at the beginning */
    /* of the stream.                               */
    pIStream -> Seek(li, STREAM_SEEK_SET, NULL);
    if
    (
      ::CoUnmarshalInterface 
      (
        pIStream,
        riid,
        (void **)ppInterfaceReceiver
      ) != S_OK
    )
    {
      /* Since unmarshalling has failed, we call   */
      /* CoReleaseMarshalData() to destroys the    */
      /* previously marshaled data packet contained*/
      /* in pIStream.                              */
      ::CoReleaseMarshalData(pIStream);
    }
    /* When pIStream is Release()'d the underlying global memory*/
    /* used to store the bytes of the stream is also freed.     */
    pIStream -> Release();
    pIStream = NULL;
  }
}

LowLevelInProcUnmarshalInterface() is designed to deserialize an input stream object (represented by an IStream pointer) back into an interface pointer. If the unmarshaling is done inside a thread of an apartment different from the one which first performed the marshaling, this interface pointer is a proxy. Note that if, for some reason, the receiving apartment happens to be the same apartment from which the marshaling was done, the resultant pointer is a direct interface pointer and is not a proxy. One again, COM will internally handle this transparently for the developer but it is useful to know this level of detail.

Let us analyse this function carefully:

  1. LowLevelInProcUnmarshalInterface() will use any stream object that supports the IStream interface. After all, a stream simply needs to store a buffer of bytes. It is the contents of this buffer of bytes (i.e., the marshaled data packet) which are important. The stream object is simply a carrier.
  2. Whatever form it takes, LowLevelInProcUnmarshalInterface() will first reset its internal position pointer to the beginning (via IStream::Seek()).
  3. It then calls the CoUnmarshalInterface() API to perform the unmarshaling operation. This API takes in a reference to an IID which must be supplied by the caller of our LowLevelInProcUnmarshalInterface() function. CoUnmarshalInterface() will return to us an interface pointer via the third "out" parameter.
  4. Now if, for some reason, the call to CoUnmarshalInterface() fails, we must make a call to CoReleaseMarshalData() to destroy the marshaled data packet contained in the stream object. If the call to CoUnmarshalInterface() succeeds, CoReleaseMarshalData() will be called automatically by CoUnmarshalInterface().
  5. Whether CoUnmarshalInterface() succeeds or fails, we will need to call Release() on the input IStream pointer. You will probably see the similarity between LowLevelInProcUnmarshalInterface() and CoGetInterfaceAndReleaseStream(): both will release the input stream object.

The purpose of calling CoReleaseMarshalData() is to free whatever resources being held for the marshaled data packet. The MSDN documentation for CoReleaseMarshalData() presents a very good analogy: the data packet can be thought of as a reference to the original object, just as if it were another interface pointer being held on the object. Like a real interface pointer, that data packet must be released at some point. The call to CoReleaseMarshalData() performs the releasing of the data packets and is analogous to the use of IUnknown::Release() to release an interface pointer.

Note that it is not enough to simply call Release() on the stream object represented by the IStream interface pointer. Doing so will free the memory buffer occupied by the stream object. Calling IStream::Release() without first calling CoReleaseMarshalData() is as good as deleting a memory reference to an interface pointer without calling Release() on it first, resulting in reference undercounting and eventually memory leakage.

Note that once CoReleaseMarshalData() is invoked, either through CoUnmarshalInterface() or through an explicit call, the marshaled data packet will no longer be available. This affirms the concept of "Normal" or "One-Time" marshaling.

Demonstrating The Higher-Level CoMarshalInterThreadInterfaceInStream() And CoGetInterfaceAndReleaseStream() APIs

This demonstration uses a higher-level set of API functions provided by COM to achieve marshaling and unmarshaling. This set of APIs are CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream(). In fact, these two functions actually encapsulate the same logic contained within LowLevelInProcMarshalInterface() and LowLevelInProcUnmarshalInterface() which we studied previously. More on this later.

Please refer to the code listings of the DemonstrateInterThreadMarshallingUsingIStream() and ThreadFunc_MarshalUsingIStream() functions:

/* This function demonstrates a proper way of marshalling an interface    */
/* pointer from one apartment to another.                                 */
/*                                                                        */
/* The method used here involves a stream of bytes that stores the thread-*/
/* independent serialized bytes of an interface pointer. This stream of   */
/* bytes (headed by an IStream interface pointer) is then passed to a     */
/* destination thread of an apartment distinct from the original interface*/
/* pointer's own apartment.                                               */
/*                                                                        */
/* The destination thread then deserializes the bytes into a proxy to the */
/* original interface pointer.                                            */
/*                                                                        */
void DemonstrateInterThreadMarshallingUsingIStream
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
)
{
  HANDLE  hThread = NULL;
  DWORD   dwThreadId = 0;
  IUnknown*  pIUnknown = NULL;
  IStream*  pIStream = NULL;
  /* QI the original interface pointer for its IUnknown interface. */
  spISimpleCOMObject2 -> QueryInterface (IID_IUnknown, (void**)&pIUnknown);
 /*Once we get the IUnknown pointer,we serialize it into a stream of bytes*/
  if (pIUnknown)
  {
    ::CoMarshalInterThreadInterfaceInStream
    (
      __uuidof(ISimpleCOMObject2Ptr),
      pIUnknown,
      &pIStream
    );
    pIUnknown -> Release();
    pIUnknown = NULL;
  }
  if (pIStream)
  {
    /* Demonstrate the use of a stream of bytes to marshal an interface */
    /* pointer from one thread to another. */
    hThread = CreateThread
    (
      (LPSECURITY_ATTRIBUTES)NULL,
      (SIZE_T)0,
      (LPTHREAD_START_ROUTINE)ThreadFunc_MarshalUsingIStream,
      (LPVOID)pIStream,
      (DWORD)0,
      (LPDWORD)&dwThreadId
    );
    ThreadMsgWaitForSingleObject(hThread, INFINITE);
    CloseHandle (hThread);
    hThread = NULL;
    /* Note : do not call pIStream -> Release(). */
    /* This is done when receiving thread calls  */
    /* CoGetInterfaceAndReleaseStream().         */
  }
}
/* This thread function obtains a pointer to  */
/* an ISimpleCOMObject2 interface via IStream */
/* which contains an apartment-independent    */
/* serialized bytes of an interface pointer.  */
/*                                            */
/* This set of bytes can be de-serialized into*/
/* a proxy to the interface pointer.          */
/*                                            */
DWORD WINAPI ThreadFunc_MarshalUsingIStream(LPVOID lpvParameter)
{
  /* The IStream object may be passed from one thread */
  /* to another. It is thread-independent.            */
  LPSTREAM  pIStream = (LPSTREAM)lpvParameter;
  ISimpleCOMObject2* pISimpleCOMObject2 = NULL;
  /* This thread enters an STA.*/
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* Note the id of this thread.   */
  /* Let's say this is thread_id_3.*/
  DisplayCurrentThreadId();
  /* Deserialize the byte contents of the IStream object */
  /* into an actual interface pointer to be used only    */
  /* within this thread.                                 */
  /*                                                     */
  /* The interface pointer will not be a direct pointer. */
  /* It will be a proxy to the original pointer.         */
  if (pIStream)
  {
    ::CoGetInterfaceAndReleaseStream
    (
      pIStream,
      __uuidof(ISimpleCOMObject2Ptr),
      (void**)&pISimpleCOMObject2
    );
  }
  if (pISimpleCOMObject2)
  {
    /* Call the TestMethod1() using the proxy.    */
    /* You will note that the thread id will      */
    /* not be thread_id_3. It will be thread_id_1 */
    /* (main()'s thread id) which is the id       */
    /* of the STA thread in which the object      */
    /* was originally created.                    */
    pISimpleCOMObject2 -> TestMethod1();
    /* You may be surprised that the return value   */
    /* of Release() is actually zero, showing that  */
    /* it is the proxy that is Release()'d, not the */
    /* original interface pointer.                  */
    pISimpleCOMObject2 -> Release();
    pISimpleCOMObject2 = NULL;
  }
  ::CoUninitialize();
  return 0;
}

The general synopsis of DemonstrateInterThreadMarshallingUsingIStream() and ThreadFunc_MarshalUsingIStream() is listed below:

  1. DemonstrateInterThreadMarshallingUsingIStream() is to take main()'s spISimpleCOMObject2 and marshal it into a stream of bytes containing the marshaled data packet of spISimpleCOMObject2's ISimpleCOMObject2 interface.
  2. It will then start a thread (headed by ThreadFunc_MarshalUsingIStream()) which takes a pointer to this stream as parameter.
  3. It will then take a back seat, hand over control to ThreadFunc_MarshalUsingIStream(), and wait for the thread to complete.
  4. ThreadFunc_MarshalUsingIStream() is designed to be an STA thread which unmarshals the stream passed to it from DemonstrateInterThreadMarshallingUsingIStream().
  5. The unmarshaled stream becomes a proxy for the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2.
  6. We will then demonstrate the effectiveness of the proxy.

You will observe that the above synopsis is very similar to that of DemonstrateInterThreadMarshallingUsingLowLevelAPI() and ThreadFunc_MarshalUsingLowLevelAPI() which we studied earlier.

In fact, the internals of DemonstrateInterThreadMarshallingUsingIStream() and ThreadFunc_MarshalUsingIStream() are also very similar to that of DemonstrateInterThreadMarshallingUsingLowLevelAPI() and ThreadFunc_MarshalUsingLowLevelAPI() respectively.

So similar, in fact, that we can later swap some function calls between them. More on this later. For now, let us thoroughly go through these two functions in detail:

  1. DemonstrateInterThreadMarshallingUsingIStream() will take as parameter a reference to an ISimpleCOMObject2Ptr object.
  2. We know that main() will invoke DemonstrateInterThreadMarshallingUsingIStream() and pass its "spISimpleCOMObject2" as parameter. We will assume this fact from here onwards and treat the spISimpleCOMObject2 parameter as equivalent to main()'s spISimpleCOMObject2.
  3. DemonstrateInterThreadMarshallingUsingIStream() will use the CoMarshalInterThreadInterfaceInStream() API to serialize the ISimpleCOMObject2 interface of spISimpleCOMObject2 into a marshaled data packet contained inside a stream object which is represented by an IStream pointer ("pIStream").
  4. A thread headed by ThreadFunc_MarshalUsingIStream() is then started. The IStream pointer "pIStream" is passed to this thread function as a parameter.
  5. DemonstrateInterThreadMarshallingUsingIStream() then sits idle while waiting for the newly started thread to complete. The function ThreadMsgWaitForSingleObject() is used to perform the waiting.
  6. Based on the assumption in point 2, we can say then that the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2 has been marshaled to ThreadFunc_MarshalUsingIStream().
  7. ThreadFunc_MarshalUsingIStream() starts up by entering an STA. By this time, it has already converted its parameter into an LPSTREAM ("pIStream").
  8. It then displays its thread ID. Let's say this ID is thread_id_3.
  9. The thread function then calls the CoGetInterfaceAndReleaseStream() function to convert "pIStream" into a pointer to the interface ISimpleCOMObject2 (i.e., "pISimpleCOMObject2").
  10. pISimpleCOMObject2 is actually a proxy to the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2.
  11. We then invoke TestMethod1() on pISimpleCOMObject2. The ID of the thread that executes this method will be displayed. You will note that this ID is not thread_id_3. It will be thread_id_1. That is, it is the ID of main()'s thread.
  12. Just as we have seen previously in ThreadFunc_MarshalUsingLowLevelAPI(), this is not surprising because main()'s thread is spISimpleCOMObject2's original STA thread.
  13. We have thus shown the effectiveness of the interface pointer proxy (in its ability to successfully invoke a method) produced out of our marshaling and unmarshaling procedures using the higher-level APIs CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream().
  14. We have also shown that the proxy has fulfilled its responsibility to pass execution control to its original pointer's apartment.

Experimentations

We mentioned earlier that CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream() encapsulate the same logic contained within our own LowLevelInProcMarshalInterface() and LowLevelInProcUnmarshalInterface() functions. I also mentioned that calls to them can be correspondingly swapped. I encourage the reader to try out the experiments described below to demonstrate these points.

Experiment 1

Go to function DemonstrateInterThreadMarshallingUsingIStream() where the Win32 API CoMarshalInterThreadInterfaceInStream() was called, change this to LowLevelInProcMarshalInterface():

void DemonstrateInterThreadMarshallingUsingIStream
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
)
{
  HANDLE  hThread = NULL;
  DWORD   dwThreadId = 0;
  IUnknown*  pIUnknown = NULL;
  IStream*  pIStream = NULL;
  /* QI the original interface pointer for its IUnknown interface. */
  spISimpleCOMObject2 -> QueryInterface (IID_IUnknown, (void**)&pIUnknown);
/*Once we get the IUnknown pointer,we serialize it into a stream of bytes.*/
  if (pIUnknown)
  {
    /* Comment out... */
    /*
    ::CoMarshalInterThreadInterfaceInStream
    (
      __uuidof(ISimpleCOMObject2Ptr),
      pIUnknown,
      &pIStream
    );
    */
    /* Call our own function ... */
    pIStream = LowLevelInProcMarshalInterface<ISimpleCOMObject2>
      (spISimpleCOMObject2, __uuidof(spISimpleCOMObject2));
    pIUnknown -> Release();
    pIUnknown = NULL;
  }
  ...
  ...
  ...

Leave the code in function ThreadFunc_MarshalUsingIStream() totally alone. Execute the VCTest01 test program. You will find that the stream object created in LowLevelInProcMarshalInterface() will be usable by CoGetInterfaceAndReleaseStream() in ThreadFunc_MarshalUsingIStream(). The unmarshaling will succeed normally.

Experiment 2

Before proceeding this second experiment, undo the changes to function DemonstrateInterThreadMarshallingUsingIStream() (i.e., make sure that the call to CoMarshalInterThreadInterfaceInStream() is re-instated and the call to LowLevelInProcMarshalInterface() removed).

Now, in function ThreadFunc_MarshalUsingIStream(), where the function CoGetInterfaceAndReleaseStream() was called, change this to LowLevelInProcUnmarshalInterface():

DWORD WINAPI ThreadFunc_MarshalUsingIStream(LPVOID lpvParameter)
{
  /* The IStream object may be passed from one thread */
  /* to another. It is thread-independent.            */
  LPSTREAM  pIStream = (LPSTREAM)lpvParameter;
  ISimpleCOMObject2* pISimpleCOMObject2 = NULL;
  /* This thread enters an STA.*/
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* Note the id of this thread.   */
  /* Let's say this is thread_id_3.*/
  DisplayCurrentThreadId();
  /* Deserialize the byte contents of the IStream object */
  /* into an actual interface pointer to be used only    */
  /* within this thread.                                 */
  /*                                                     */
  /* The interface pointer will not be a direct pointer. */
  /* It will be a proxy to the original pointer.         */
  if (pIStream)
  {
    /* Comment out ... */
    /*::CoGetInterfaceAndReleaseStream
    (
      pIStream,
      __uuidof(ISimpleCOMObject2Ptr),
      (void**)&pISimpleCOMObject2
    );*/
    /* Call our own function ... */
    LowLevelInProcUnmarshalInterface<ISimpleCOMObject2>
      (pIStream, __uuidof(ISimpleCOMObject2Ptr), &pISimpleCOMObject2);
  }
  ...
  ...
  ...

Execute the VCTest01 test program. You will find that the stream object created by CoMarshalInterThreadInterfaceInStream() will be usable by our own LowLevelInProcUnmarshalInterface() function in ThreadFunc_MarshalUsingIStream(). The stream objects used by these functions adhere to a standard which make them interchangeable.

The inner science and technology behind marshaling, unmarshaling, proxies and stubs form a subject worthy of study on their own. We will not be covering this topic here in this article. For more information, study Don Box's book "Essential COM".

Demonstrating The High-Level Global Interface Table (GIT)

This demonstration uses probably the highest-level set of API functions provided by COM to achieve marshaling and unmarshaling. This set of APIs work on a COM feature known as the Global Interface Table (GIT).

Our previous two examples used normal or one-time marshaling, so-called because the marshaled data packet can only be unmarshaled once. There are scenarios, however, in which it would be much more convenient to marshal an interface pointer once and then unmarshal it multiple times into multiple apartments. To achieve this, COM supports the concept of table marshaling.

Table marshaling is a COM feature which allows the permanent storage of a marshaled data packet somewhere in an application. This data packet may then be unmarshaled multiple times. This is achieved by using the MSHLFLAGS_TABLESTRONG or MSHLFLAGS_TABLEWEAK flags when calling CoMarshalInterface(). However, one down side to this feature is that it does not support the marshaling of proxies. This is a disappointment because table marshaling of proxies is most useful in many situations especially in distributed applications.

To cater to this specific requirement, Windows NT 4.0 Service Pack 3 introduced the Global Interface Table (GIT). Only one GIT is implemented in every COM-based application. It is a process-wide repository which contains marshaled interface pointers that can be unmarshaled multiple times within an application. The GIT can work with both direct interface pointers as well as proxies.

Please refer to the code listings of the DemonstrateInterThreadMarshallingUsingGIT() and ThreadFunc_MarshalUsingGIT() functions:

/*This function demonstrates a proper way of marshalling an interface    */
/*pointer from one apartment to another.                                 */
/*                                                                       */
/*The method used here involves the Global Interface Table (GIT) which   */
/*is used to store interface pointers from various apartments in a process*/
/*-wide repository.                                                      */
/*                                                                       */
/*A thread of a destination apartment can obtain a proxy to an interface */
/*pointer from this table.                                               */
/*                                                                       */
void DemonstrateInterThreadMarshallingUsingGIT
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
)
{
  HANDLE   hThread = NULL;
  DWORD    dwThreadId = 0;
  IUnknown*   pIUnknown = NULL;
  IGlobalInterfaceTable* pIGlobalInterfaceTable = NULL;
  DWORD    dwCookie = 0;
/*There is a single instance of the global interface table per process.   */
/*Hence all calls in a process to create it will return the same instance.*/
/*We can get an interface pointer to this GIT here in this function.Later,*/
/*another thread is able to retrieve the same GIT interface pointer via   */
/*another call to ::CoCreateInstance().                                   */
  ::CoCreateInstance
  (
    CLSID_StdGlobalInterfaceTable,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IGlobalInterfaceTable,
    (void **)&pIGlobalInterfaceTable
  );
  if (pIGlobalInterfaceTable)
  {
    /* QI the original interface pointer for its IUnknown interface. */
    spISimpleCOMObject2 -> QueryInterface
      (IID_IUnknown, (void**)&pIUnknown);
    if (pIUnknown)
    {
      /* Register this interface pointer in GIT.    */
      /* A cookie, identifying the interface pointer*/
      /* is returned.                               */
      /* No need to call pIUnknown -> AddRef().     */
      /* Another thread can retrieve the pIUnknown  */
      /* using the cookie.                          */
      pIGlobalInterfaceTable -> RegisterInterfaceInGlobal
      (
        pIUnknown,
        __uuidof(ISimpleCOMObject2Ptr),
        &dwCookie
      );
      pIUnknown -> Release();
      pIUnknown = NULL;
    }
  }
  if (dwCookie)
  {
    /* Demonstrate the use of GIT to marshal an interface */
    /* pointer from one thread to another.                */
    /* The cookie of the interface pointer is passed as a */
    /* parameter to the thread function.                  */
    hThread = CreateThread
    (
      (LPSECURITY_ATTRIBUTES)NULL,
      (SIZE_T)0,
      (LPTHREAD_START_ROUTINE)ThreadFunc_MarshalUsingGIT,
      (LPVOID)dwCookie,
      (DWORD)0,
      (LPDWORD)&dwThreadId
    );
    /* Get this thread to wait until the new thread ends.*/
    /* In the meantime, we must continue to process      */
    /* Windows messages.                                 */
    ThreadMsgWaitForSingleObject(hThread, INFINITE);
    CloseHandle (hThread);
    hThread = NULL;
    pIGlobalInterfaceTable -> RevokeInterfaceFromGlobal(dwCookie);
    dwCookie = 0;
  }
  if (pIGlobalInterfaceTable)
  {
    pIGlobalInterfaceTable -> Release();
    pIGlobalInterfaceTable = NULL;
  }
}
/* This is a thread start function that demonstrates */
/* the use of a Global Interface Table to transfer   */
/* interface pointers from one thread to another.    */
DWORD WINAPI ThreadFunc_MarshalUsingGIT(LPVOID lpvParameter)
{
  /* The cookie of the interface registered in the GIT */
  /* is passed by the thread parameter. */
  DWORD    dwCookie = (DWORD)lpvParameter; 
  ISimpleCOMObject2*  pISimpleCOMObject2 = NULL;
  IGlobalInterfaceTable* pIGlobalInterfaceTable = NULL;
  /* Make this thread an STA thread. */
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* There is a single instance of the global interface */
  /* table per process.                                 */
  /* Hence all calls in a process to create it will     */
  /* return the same instance.                          */
  CoCreateInstance
  (
    CLSID_StdGlobalInterfaceTable,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IGlobalInterfaceTable,
    (void **)&pIGlobalInterfaceTable
  );
  if (pIGlobalInterfaceTable)
  {
    /* Display the id of this thread. Let's say this*/
    /* is thread_id_4. */
    DisplayCurrentThreadId();
    /* Retrieve the interface pointer from the GIT. */
    /* What is returned is actually a proxy to the  */
    /* original interface pointer created in the    */
    /* main() function.                             */
    pIGlobalInterfaceTable -> GetInterfaceFromGlobal
    (
      dwCookie,
      __uuidof(ISimpleCOMObject2Ptr),
      (void**)&pISimpleCOMObject2
    );
    if (pISimpleCOMObject2)
    {
      /* Display the id of the thread which executes */
      /* TestMethod1(). This should be thread_id_1.  */
      /* That is, it is the id of main()'s thread.   */
      pISimpleCOMObject2 -> TestMethod1();
      /* Release() the proxy interface pointer.         */
      /* The current ref count of the proxy is returned.*/
      /* This ref count may not tally with the original */
      /* interface pointer.                             */
      pISimpleCOMObject2 -> Release();
      pISimpleCOMObject2 = NULL;
    }
    pIGlobalInterfaceTable -> Release();
    pIGlobalInterfaceTable = NULL;
  }
  ::CoUninitialize();
  return 0;
}

The general synopsis of DemonstrateInterThreadMarshallingUsingGIT() and ThreadFunc_MarshalUsingGIT() is listed below:

  1. DemonstrateInterThreadMarshallingUsingGIT() is to take a pointer to the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2 and register it into the Global Interface Table.
  2. The registration process will return a cookie which uniquely identifies the interface pointer registered.
  3. DemonstrateInterThreadMarshallingUsingGIT() will then start a thread (headed by ThreadFunc_MarshalUsingGIT()) which takes the cookie as parameter.
  4. It will then take a back seat, hand over control to ThreadFunc_MarshalUsingGIT(), and wait for the thread to complete.
  5. When the ThreadFunc_MarshalUsingGIT() thread completes, DemonstrateInterThreadMarshallingUsingGIT() will remove the original interface pointer from the GIT.
  6. ThreadFunc_MarshalUsingGIT() is designed to be an STA thread which retrieves the interface pointer stored inside the GIT. The GIT will internally perform the work required to unmarshal the interface pointer to the thread's apartment.
  7. The unmarshaled interface pointer is a proxy for the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2.
  8. We will then demonstrate the effectiveness of the proxy.

Let us now go through these two functions in detail:

  1. DemonstrateInterThreadMarshallingUsingGIT() will first create an instance of the Global Interface Table by calling CoCreateInstance() and using the GIT's coclass ID CLSID_StdGlobalInterfaceTable and requesting for the IGlobalInterfaceTable interface.
  2. Because there is only one single instance of the Global Interface Table per process, all calls in a process to create it will return the same instance.
  3. DemonstrateInterThreadMarshallingUsingGIT() will then register the ISimpleCOMObject2 interface of its input spISimpleCOMObject2 interface pointer into the GIT via the IGlobalInterfaceTable::RegisterInterfaceInGlobal() method.
  4. A cookie which identifies the interface pointer just registered will be returned.
  5. A new thread (headed by ThreadFunc_MarshalUsingGIT()) is started. The cookie is passed as a parameter.
  6. DemonstrateInterThreadMarshallingUsingGIT() will then use the ThreadMsgWaitForSingleObject() helper function to wait for the completion of the ThreadFunc_MarshalUsingGIT() thread.
  7. When ThreadFunc_MarshalUsingGIT() completes, the registered interface pointer is removed from the GIT via the IGlobalInterfaceTable::RevokeInterfaceFromGlobal() method.
  8. The pointer to the GIT is then released.
  9. ThreadFunc_MarshalUsingGIT() starts life by entering an STA.
  10. It converts its thread parameter into a DWORD cookie.
  11. It then creates a GIT. The same process-wide GIT (created in DemonstrateInterThreadMarshallingUsingGIT()) is returned.
  12. We then call our utility function DisplayCurrentThreadId() to display this thread's ID. Let's say, this ID is thread_id_4.
  13. We then retrieve the unmarshaled ISimpleCOMObject2 interface pointer of main()'s spISimpleCOMObject2 by calling IGlobalInterfaceTable::GetInterfaceFromGlobal() and using the cookie passed to ThreadFunc_MarshalUsingGIT().
  14. The output of GetInterfaceFromGlobal() is a proxy to the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2. This proxy is stored in a local ISimpleCOMObject2 interface pointer ("pISimpleCOMObject2").
  15. We then call TestMethod1() using our proxy pISimpleCOMObject2. The ID of the thread which executes TestMethod1() will be displayed. Note that this will not be thread_id_4. It will be thread_id_1 (main()'s thread ID).
  16. This is logical because the object behind pISimpleCOMObject2 is an STA object created inside main()'s thread. Hence main()'s thread is the STA thread of the object.
  17. We have thus shown the effectiveness of the interface pointer proxy (in its ability to successfully invoke a method) produced out of our marshaling and unmarshaling procedures using the GIT.
  18. We have also shown that the proxy has fulfilled its responsibility to pass execution control to its original object's apartment.

The inter-apartment marshaling interaction using the GIT can be illustrated in the diagram below:

The GIT is most useful when many threads need to access one single interface pointer.

Demonstrating An Invalid And Dangerous Cross-Thread Transfer Of Interface Pointers

Part of any treatise on marshaling should include some cautionary notes against the invalid and dangerous passing of interface pointers across threads. This section presents two example programs which show exactly this.

The first example demonstrates a clear instance of the violation of thread access rules. It, however, also shows how such careless code can still function normally without any apparent hiccups.

The second example uses the exact same test code as the first but shows clearly the fatal effects of the direct but illegal transfer of an interface pointer across threads. The second example uses a COM object written in Visual Basic.

Example 1

Please refer to the code listings of the DemonstrateDangerousTransferOfInterfacePointers() and ThreadFunc_DangerousTransferOfInterfacePointers() functions:

/* This function demonstrates an illegal and dangerous method of       */
/* transferring an interface pointer from one apartment to another.    */
/* This method will still work (albeit illegally) for an ATL generated */
/* COM DLL Server AND a client app deliberately developed to avoid     */
/* threading problems.                                                 */
void DemonstrateDangerousTransferOfInterfacePointers
(
  ISimpleCOMObject2Ptr& spISimpleCOMObject2
)
{
  HANDLE hThread = NULL;
  DWORD  dwThreadId = 0;
  /* We are going to DIRECTLY pass an ISimpleCOMObject2 interface  */
  /* pointer to a thread via thread parameter.                     */
  /* We need to AddRef() the interface pointer before passing it   */
  /* to a client thread. The client thread must later Release() it.*/
  spISimpleCOMObject2 -> AddRef();
  hThread = CreateThread
  (
    (LPSECURITY_ATTRIBUTES)NULL,
    (SIZE_T)0,
    (LPTHREAD_START_ROUTINE)ThreadFunc_DangerousTransferOfInterfacePointers,
    (LPVOID)((ISimpleCOMObject2*)spISimpleCOMObject2),
    (DWORD)0,
    (LPDWORD)&dwThreadId
  );
  ThreadMsgWaitForSingleObject(hThread, INFINITE);
  CloseHandle (hThread);
  hThread = NULL;
}
/* This thread function receives an interface pointer DIRECTLY from */
/* another thread. What we have is not a proxy but a DIRECT pointer */
/* to the original interface pointer.                               */
DWORD WINAPI ThreadFunc_DangerousTransferOfInterfacePointers
(
  LPVOID lpvParameter
)
{
  /* Make this an STA thread. */
  ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  /* Display the id of this thread. */
  /* Let's say this is thread_id_5. */
  DisplayCurrentThreadId();
  if (1)
  {
    /* Directly cast the thread parameter into */
    /* an ISimpleCOMObject2 interface pointer. */
    ISimpleCOMObject2* pISimpleCOMObject2
      = (ISimpleCOMObject2*)lpvParameter;
    /* The id of the thread executing TestMethod1() */
    /* will be the id of this thread : thread_id_5. */
    pISimpleCOMObject2 -> TestMethod1();
    /* Calling Release() will affect the original object's */
    /* reference count.                                    */
    pISimpleCOMObject2 -> Release();
    pISimpleCOMObject2 = NULL;
  }
  ::CoUninitialize();
  return 0;
}

The general synopsis of DemonstrateDangerousTransferOfInterfacePointers() and ThreadFunc_DangerousTransferOfInterfacePointers() is listed below:

  1. DemonstrateDangerousTransferOfInterfacePointers() will take a pointer to the ISimpleCOMObject2 interface of main()'s spISimpleCOMObject2 and directly pass it onto a thread as a thread parameter.
  2. It will then take a back seat, hand over control to ThreadFunc_DangerousTransferOfInterfacePointers(), and wait for the thread to complete.
  3. ThreadFunc_DangerousTransferOfInterfacePointers() is designed to be an STA thread.
  4. It converts its LPVOID parameter into a pointer to ISimpleCOMObject2 and proceeds to call TestMethod1() via this pointer.
  5. The function call will succeed, demonstrating a seemingly harmless way of transferring and using an interface pointer across apartments.
  6. ThreadFunc_DangerousTransferOfInterfacePointers() then releases the ISimpleCOMObject2 interface pointer and exits.

Let us now go through these two functions in detail:

  1. DemonstrateDangerousTransferOfInterfacePointers() will first AddRef() the input spISimpleCOMObject2. This is done because we will later pass the ISimpleCOMObject2 interface of spISimpleCOMObject2 into ThreadFunc_DangerousTransferOfInterfacePointers().
  2. DemonstrateDangerousTransferOfInterfacePointers() will then create the thread headed by ThreadFunc_DangerousTransferOfInterfacePointers().
  3. DemonstrateDangerousTransferOfInterfacePointers() will then use the ThreadMsgWaitForSingleObject() helper function to wait for the completion of the ThreadFunc_DangerousTransferOfInterfacePointers() thread.
  4. ThreadFunc_DangerousTransferOfInterfacePointers() starts life by entering an STA.
  5. It will then call the utility function DisplayCurrentThreadId() to display its thread ID. Let's say this ID is thread_id_5.
  6. It will then convert its void pointer parameter into a pointer to the ISimpleCOMObject2 interface ("pISimpleCOMObject2").
  7. Next, the ISimpleCOMObject2::TestMethod1() method will be invoked via pISimpleCOMObject2.
  8. The ID of the thread executing TestMethod1() will be displayed. You will note that this is thread_id_5. That is, it is the thread ID of ThreadFunc_DangerousTransferOfInterfacePointers().
  9. This is not correct. The thread ID displayed through TestMethod1() should be thread_id_1 (main()'s thread ID) because the object behind pISimpleCOMObject2 is an STA object created inside main()'s thread. Hence main()'s thread is the STA thread of the object.
  10. We have shown the ostensible effectiveness of the direct interface pointer proxy (in its ability to successfully invoke a method) even though it is illegal.
  11. Execution control, however, did not pass from ThreadFunc_DangerousTransferOfInterfacePointers()'s STA to the original object's apartment.

This example could succeed due to a deliberate attempt by its developer to avoid threading problems. It is completely wrong but no ill-effects could be seen in this example. The next example will show the drastic effects of such an illegal interface pointer transfer.

Example 2

The code for this example are located in two separate folders of the source codes ZIP file: "VBSTACOMObj" and "Test Programs\VCTests\DemonstrateSTAInterThreadMarshalling\VCTest02".

The VBSTACOMObj folder contains a very simple COM object written in Visual Basic. The COM object is of coclass "ClassVBSTACOMObj". This is an STA object (like all COM objects created using Visual Basic). The interface we are interested in engaging with is "_ClassVBSTACOMObj". This interface is the only interface exposed by the ClassVBSTACOMObj coclass. This interface exposes only one method: TestMethod1(). The source code for this object is listed below:

Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Public Function TestMethod1() As Long
  MsgBox "Thread ID : 0x" & Hex$(GetCurrentThreadId())

  TestMethod1 = 0
End Function

Just like the TestMethod1() method of the other interfaces that we have worked with in previous example codes, the TestMethod1() method of the _ClassVBSTACOMObj interface of the ClassVBSTACOMObj coclass will display in a message box the ID of the currently executing thread.

The VCTest02 folder contains test codes that use the exact same logic as the test code that we have seen in "VCTest01" (which was used throughout the examples in the sections "Demonstrating The Low-Level CoMarshalInterface() And CoUnmarshalInterface() APIs", "Demonstrating The Higher-Level CoMarshalInterThreadInterfaceInStream() And CoGetInterfaceAndReleaseStream() APIs" and "Demonstrating The High-Level Global Interface Table (GIT)".)

The only difference between VCTest01 and VCTest02 is that in VCTest01, COM object coclass SimpleCOMObject2 is used while in VCTest02, ClassVBSTACOMObj is used.

I will leave it to the reader to walk through the various parts of VCTest02 (i.e., the calling of functions DemonstrateInterThreadMarshallingUsingLowLevelAPI(), DemonstrateInterThreadMarshallingUsingIStream() and DemonstrateInterThreadMarshallingUsingGIT()). The reader will note that these functions will work correctly as we have previously witnessed in VCTest01. My point in preparing these functions in VCTest02 is to show that ClassVBSTACOMObj, a COM object created using VB, works correctly when subjected to the marshaling techniques presented earlier, thus streng