Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / ATL
Article

Understanding The COM Single-Threaded Apartment Part 2

Rate me:
Please Sign up or sign in to vote.
4.90/5 (57 votes)
18 Feb 2005CPOL71 min read 255.5K   2.2K   169   60
Learn the fundamental principles of the COM Single-Threaded Apartment Model by code examples.

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:

Image 2

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:

Image 3

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:

Image 4

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:

VB
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
VB
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 strengthening the legitimacy (especially LowLevelInProcMarshalInterface() and LowLevelInProcUnmarshalInterface()) of these techniques.

Now, when we next analyze DemonstrateDangerousTransferOfInterfacePointers(), we will see that the execution of this function in VCTest02 will differ from that in VCTest01. Please refer to the source code of the function ThreadFunc_DangerousTransferOfInterfacePointers():

/* 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 */
    /* a _ClassVBSTACOMObj interface pointer.  */
    _ClassVBSTACOMObj* p_ClassVBSTACOMObj
      = (_ClassVBSTACOMObj*)lpvParameter;
    /* A crash will occur here when TestMethod1() executes. */
    p_ClassVBSTACOMObj -> TestMethod1();
    /* Calling Release() will affect the original object's */
    /* reference count.                                    */
    p_ClassVBSTACOMObj -> Release();
    p_ClassVBSTACOMObj = NULL;
  }
  ::CoUninitialize();
  return 0;
}

You will note that when we execute the code listed in bold above:

p_ClassVBSTACOMObj -> TestMethod1();

a crash will ensue. The counterpart of the above line in VCTest01 is:

pISimpleCOMObject2 -> TestMethod1();

Let us analyze the situation: we know that both coclasses ClassVBSTACOMObj and SimpleCOMObject2 will instantiate STA objects. We know that although both calls are illegal, there are no concurrent multi-threaded access to these objects when their TestMethod1() methods are invoked (in both cases, one thread will invoke TestMethod1() while the other hibernates waiting for the first thread to end (via ThreadMsgWaitForSingleObject())). However, the call in VCTest02 resulted in an application runtime error whereas that in VCTest01 did not. What is the difference between them?

The answer is Thread Local Storage (TLS). Recall the section "Benefits Of Using STA" in part 1:

"Because STA objects are always accessed from the same thread, it is said to have thread affinity. And with thread affinity, STA object developers can use thread local storage to keep track of an object's internal data. Visual Basic and MFC use this technique for development of COM objects and hence are STA objects."

Because COM objects developed using Visual Basic use TLS internally, it is not hard to imagine where the crash came from. Chances are, when TestMethod1() is invoked, the VB Runtime Engine looks up the current thread for its own locally stored data. It either cannot find this data, or it assumes that it has found it and proceeds to use what is likely to be random data.

This example clearly shows that we cannot carelessly pass an interface pointer from one thread to another. The best policy remains that we should always use the marshaling APIs to perform any cross-thread transfer of interface pointers. Threads of the same apartment do not require marshaling and calls are made directly without the need for proxies and stubs. This is so even if marshaling APIs are used to transfer interface pointers across the threads. COM will see to this and will arrange for the direct use of interface pointers. Hence it is a good programming practice to always use marshaling.

Demonstrating Advanced STA

This section will begin a long and deep study into advanced STA concepts. We make a close study of an STA COM object and a test client program which is used to access the methods of the object and to receive an event fired from the object. The central point of this study is the demonstration of the object's ability to fire its event (to the test client) from a thread external to the one in which it was instantiated. We do this via a custom-developed C++ class named CComThread which is a wrapper/manager for a Win32 thread that contains COM objects or references to COM objects. CComThread also provides useful utilities that help in inter-thread COM method calls.

Because our STA COM object internally uses the CComThread class to perform its crucial thread management operations, we must analyze this class first before we begin our study of the object.

The CComThread Class

The source code for this class is listed in "Shared\ComThread.h". CComThread encapsulates and manages a Win32 STA thread. The following are the features of this manager class:

  1. CComThread allows a LPVOID parameter to be passed to the user-supplied thread entry point function.
  2. CComThread allows interface pointers to be marshaled from a client thread to the CComThread thread.
  3. CComThread performs the unmarshaling of interface pointers automatically and maintains a vector of unmarshaled interface pointers.

Usage Of CComThread

The following is a summary of the way to use the CComThread class:

  • The CComThread class can be instantiated without any parameters.
  • A user would need to supply at minimum a thread "startup" function via CComThread::SetStartAddress().
  • The signature of the startup function is modeled after the signature of the standard LPTHREAD_START_ROUTINE.
  • A user may opt to supply a parameter for its startup function via CComThread::SetThreadParam(). Let's call this parameter the "high-level" parameter.
  • This parameter may be retrieved by calling the CComThread::GetThreadParam() function.
  • Note also that when the user-supplied startup function is started internally, a pointer to its managing CComThread object is supplied as the official function parameter. In order to obtain the "high-level" parameter passed via SetThreadParam(), the CComThread::GetThreadParam() function must be called from within the startup function.
  • Notice that I initially used quotations for "startup". This is because, internally, CComThread will supply its own private thread entry-point function which will serve as the actual thread function. This private entry-point thread function will then execute the user-supplied "startup" function.
  • The CComThread::ThreadStart() function needs to be called in order for the user-supplied startup function to begin execution.
  • Interface pointers from the client thread which owns the CComThread object may be marshaled to the newly started CComThread thread by calling CComThread::AddUnknownPointer().
  • The interface pointers supplied to AddUnknownPointer() will be automatically unmarshaled by CComThread by the time the user-supplied startup function is executed.
  • To obtain the unmarshaled interface pointers, the CComThread::GetUnknownVector() function or the IUNKNOWN_VECTOR& cast operator may be used.

We will observe the above usage patterns when we analyze the example codes later. Let us now analyze the important parts of the CComThread class. We will skip analysis of some of the functions of CComThread which are self-explanatory. These will be briefly commented on when we observe their usage in the example program.

The CComThread::ThreadStart() Function

long ThreadStart()
{
  long lRet = 0;
  if (m_hThread)
  {
    CloseHandle(m_hThread);
    m_hThread = NULL;
  }
  m_hThread = (HANDLE)CreateThread
  (
    (LPSECURITY_ATTRIBUTES)NULL,
    (SIZE_T)0,
    (LPTHREAD_START_ROUTINE)(CComThreadStartFunction),
    (LPVOID)this,
    (DWORD)((m_Flags & FLAG_START_SUSPENDED) ? CREATE_SUSPENDED : 0),
    (LPDWORD)&m_dwThreadId
  );
  return lRet;
}

The ThreadStart() function uses the Win32 API CreateThread() to create a thread fronted by an internal CComThread function CComThreadStartFunction(). CComThread will detect whether its FLAG_START_SUSPENDED flag is set, and if so, will start the thread function, but suspend its execution until it is resumed by the CComThread::ThreadResume() function.

CComThreadStartFunction(), as we will soon see, will be responsible for invoking the user-supplied startup function. Note in ThreadStart() that the "this" pointer (i.e., a self-referencing pointer to the current CComThread object) is passed as the thread parameter to the CComThreadStartFunction() thread function. This "this" will also be passed later to the user-supplied startup function.

The CComThread::CComThreadStartFunction() Function

static DWORD WINAPI CComThreadStartFunction(LPVOID lpThreadParameter)
{
  CComThread* pCComThread = (CComThread*)lpThreadParameter;
  DWORD dwRet = 0;
  CoInitialize(NULL);
  /* At this point in time, the thread managed by CComThread */
  /* has just started.                                       */
  /* We take this opportunity to unmarshall the interfaces   */
  /* stored in m_mapStream.                                  */
  pCComThread -> UnMarshallInterfaces();
  dwRet = (pCComThread -> m_lpStartAddress)(pCComThread);
  /* Once user thread has completed, we clear the Stream */
  /* and IUnknown vectors.                               */
  pCComThread -> ClearVectorStream();
  pCComThread -> ClearVectorUnknown();
  CoUninitialize();
  return dwRet;
}

The CComThreadStartFunction() is the standard thread function supplied by CComThread. Let us analyze this function in detail:

  1. CComThreadStartFunction() starts up by retrieving the CComThread object that started it. This is done by casting its thread parameter to a CComThread pointer.
  2. It then enters an STA by calling CoInitialize().
  3. It then proceeds to unmarshal the interface pointers it contains. This is done in its internal function UnMarshallInterfaces().
  4. The user supplied startup function is contained in CComThread::m_lpStartAddress and this is invoked with a pointer to the current CComThread object. The passing of the pointer to the current CComThread object is necessary as it enables the user-supplied startup function to be able to interact with it (e.g., calling its GetThreadParam() function) and use it to obtain the unmarshaled interface pointers (by calling its GetUnknownVector() function).
  5. When the natural life of the startup function ends, control is passed back to CComThreadStartFunction().
  6. CComThreadStartFunction() will then clear its internal vectors (one used to contain stream objects used to store marshaled data packets of interface pointers, another used to store the unmarshaled interface pointers).
  7. The CoUninitialize() function will then be called to officially terminate the CComThread object's STA.

The CComThread::m_vectorStream Vector

The CComThread class internally maintains a vector of IStream interface pointers:

typedef vector<LPSTREAM>     ISTREAM_VECTOR;
ISTREAM_VECTOR         m_vectorStream;

This stream vector is "m_vectorStream". It is filled with IStream interface pointers during the execution of the AddUnknownPointer() function which we will examine next.

The CComThread::AddUnknownPointer() Function

/* Note that AddUnknownPointer() must be called outside the thread.*/
long AddUnknownPointer(LPUNKNOWN& lpUnknown)
{
  IStream* pIStreamTemp = NULL;
  HRESULT  hrTemp = S_OK;
  long    lRet = 0;
  EnterCriticalSection(&m_csStreamVectorAccess);
  if (lpUnknown)
  {
    /* Create stream for marshaling lpUnknown to thread.*/
    hrTemp = ::CoMarshalInterThreadInterfaceInStream
    (
      IID_IUnknown, // interface ID to marshal
      lpUnknown,  // ptr to interface to marshal
      &pIStreamTemp  // output variable
    );
    /*Place stream in member variable where COM thread will look for it.*/
    /*No need to call Release() on pStream. They will be Release()'d    */
    /*when we later call CoGetInterfaceAndReleaseStream().              */
    if (pIStreamTemp)
    {
      m_vectorStream.push_back(pIStreamTemp);
    }
  }
  LeaveCriticalSection(&m_csStreamVectorAccess);
  return lRet;
}

The AddUnknownPointer() function is called by a CComThread user to marshal an interface pointer to the user-supplied thread managed by CComThread. It takes a reference to an IUnknown pointer and calls the CoMarshalInterThreadInterfaceInStream() API to serialize it to a stream of bytes contained in a stream object represented by an IStream pointer. This IStream pointer is then pushed into "m_vectorStream". Throughout, the critical section object m_csStreamVectorAccess is used to ensure synchronized access to the m_vectorStream vector.

The CComThread::m_vectorUnknown Vector

The CComThread class internally maintains a vector of IUnknown interface pointers:

typedef vector<LPUNKNOWN>     IUNKNOWN_VECTOR;
IUNKNOWN_VECTOR             m_vectorUnknown;

This vector of IUnknown interface pointers is "m_vectorUnknown". This vector is filled with IUnknown interface pointers during the execution of the internal CComThread::UnMarshallInterfaces() function. The m_vectorUnknown is cleared via the internal CComThread::ClearVectorUnknown() function.

The CComThread::UnMarshallInterfaces() Function

/* Note that UnMarshallInterfaces() must only be called */
/* in the thread (managed by this object).              */
long UnMarshallInterfaces ()
{
  ISTREAM_VECTOR::iterator theIterator;
  int    iIndex = 0;
  HRESULT   hrTemp = S_OK;
  long    lRet = 0;
  EnterCriticalSection(&m_csStreamVectorAccess);
  /* Unmarshal interface pointers */
  for (theIterator = m_vectorStream.begin();
      theIterator != m_vectorStream.end();
      theIterator++)
  {
    IUnknown* pIUnknownTemp = NULL;
    IStream* pIStreamTemp = NULL;
    /* Get stream pointer from array where owner has placed it.*/
    pIStreamTemp = (*theIterator);
    if (pIStreamTemp)
    {
      /* Use stream pointer to create IUnknown that */
      /* we can call from this thread.              */
      hrTemp = ::CoGetInterfaceAndReleaseStream
      (
        pIStreamTemp,
        IID_IUnknown,
        (void**)&pIUnknownTemp
      );
      /* Note that at this time, pIStreamTemp will be*/
      /* Release()'d and will no longer be valid.    */
      /* Put resulting IUnknown in IUnknown pointers */
      /* vector.                                     */
      if (pIUnknownTemp)
      {
        m_vectorUnknown.push_back(pIUnknownTemp);
        /* Since we have added pIUnknownTemp into a    */
        /* collection, we have an additional reference */
        /* to it.                                      */
        pIUnknownTemp -> AddRef();
      }
    }
  }
  /* Once all the streams in the stream vector has been */
  /* unmarshalled, we no longer need the streams vector */
  /* (the streams have been Release()'d anyway and so   */
  /* are no longer valid).                              */
  ClearVectorStream();
  LeaveCriticalSection(&m_csStreamVectorAccess);
  return lRet;
}

The CComThread::UnMarshallInterfaces() function is used internally by CComThread to unmarshal all the marshaled data packets of the stream objects contained within m_vectorStream. UnMarshallInterfaces() is called inside CComThreadStartFunction() just before the user-supplied startup function is invoked.

The CComThread::WaitThreadStop() Function

 long WaitThreadStop()
 {
   HANDLE  dwChangeHandles[1];
   BOOL   bContinueLoop = TRUE;
   DWORD   dwWaitStatus = 0;
   long   lRet = 0;
   dwChangeHandles[0] = m_hThread;
   // Msg Loop while waiting for thread to exit.
   while (bContinueLoop)
   {
     // Wait for notification.
     dwWaitStatus = ::MsgWaitForMultipleObjectsEx
     (
       (DWORD)1,
       dwChangeHandles,
       (DWORD)INFINITE,
       (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE),
       (DWORD)(MWMO_INPUTAVAILABLE)
     );
     switch (dwWaitStatus)
     {
       /* First wait (thread exit) object has been signalled. */
       case WAIT_OBJECT_0 :
       {
         /* Flag to indicate stop loop. */
         bContinueLoop = FALSE;
         break;
       }
       /* Windows message has arrived.*/
       case WAIT_OBJECT_0 + 1:
       {
         MSG msg;
         /* Dispatch all windows messages in queue. */
         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
           TranslateMessage (&msg);
           DispatchMessage(&msg);
         }
         break;
       }
       default:
       {
         break;
       }
     }
   }
   return lRet;
 }

The CComThread::WaitThreadStop() function is a very significant function. As implied by its name, WaitThreadStop() ensures that the thread that CComThread manages exits completely before it returns. WaitThreadStop() is important, not that it is absolutely essential to invoke it. Its importance lies in the way it is implemented that will ensure that it works correctly when it is called.

WaitThreadStop() is very similar to another function which I promised to document fully, back in Part 1: ThreadMsgWaitForSingleObject(). This function will be analyzed in full at the end of this article. ThreadMsgWaitForSingleObject() is actually a generalization of WaitThreadStop(). I coded ThreadMsgWaitForSingleObject() after coding CComThread to provide a generic UI-thread blocking utility that could be useful in many projects.

I shall therefore defer the discussion of WaitThreadStop() until the last section which details ThreadMsgWaitForSingleObject().

In the meantime, note well that WaitThreadStop() itself will not terminate the CComThread thread. This remains the prerogative of the code that manages the CComThread object. Once the code that manages the CComThread object has issued the commands that signal to the CComThread thread to terminate, WaitThreadStop() will likely be called if it is necessary for this code to block until the thread terminates.

This concludes, for now, our exploration into CComThread. We have enough background of this class to enable us to understand the logic and intension of the example code that follow next.

Advanced STA Example Application

This section begins our study of the advanced STA application which makes use of CComThread. A general synopsis will be given before we inspect the internals of the application. The major part of this sub-section is dedicated to the in-depth analysis of the implementation code of the STA COM object. Along the way, we will also cover the topic of ATL Connection Point Proxies using highly practical debugging procedures. This special area of interest is important because it will help us understand how a COM object can access the event sinks of its client.

The main objective of this section, nevertheless, is to demonstrate how to fire the events of the STA COM object to a receiving client application from a thread external from the one in which the STA object was created. In other words, from another apartment altogether.

General Synopsis

A typical situation in a COM-based application in which threads are used involves asynchronous method calls. That is, a method call is made and the call returns immediately but the full expected results of the method will not be available immediately. The completion of the method can take a while and the application is usually notified of the official ending by the invocation of a callback function.

The application presented in this section uses a COM object which exposes an interface method that "theoretically" performs a time-consuming operation. The client will call such a method but expects to move on immediately after the method call. The object is expected to perform its long operation "off-line". When the so-called time-consuming operation completes, the object is to fire an event received by the client.

In order for the object to be able to perform its "off-line" operation, it must use a thread. For efficiency and natural flow, the operation completion event must be fired directly from within the "off-line" operation thread. The example code presented shortly will be based on this general synopsis.

The Declaration of coclass SimpleCOMObject1

We will first study the specifications of the interfaces implemented by the COM object used in this demonstration.

Our simple object will be a coclass SimpleCOMObject1. Listed below is this coclass' IDL declaration. It implements an interface named ISimpleCOMObject1 and supports a set of events of interface _ISimpleCOMObject1Events. The source code for the coclass SimpleCOMObject1 can be found in the "SimpleCOMObject1" folder of the source codes ZIP file which accompanies this article. The source code for the test application that uses SimpleCOMObject1 can be found in "Test Programs\VBTest". This client program is written in Visual Basic.

 [
  uuid(11EF2E3F-9887-4530-8EE0-D8A57D69653A),
  helpstring("SimpleCOMObject1 Class")
 ]
 coclass SimpleCOMObject1
 {
  [default] interface ISimpleCOMObject1;
  [default, source] dispinterface _ISimpleCOMObject1Events;
 };

A side note on interfaces and coclasses

Please note the distinction between the meaning of an interface and that of a coclass. An interface is a generic specification for a group of functionality which must be provided in its entirety by an implementing object. It is programming language independent. That is, its specification does not dictate the programming language which must be used to implement it. Developers may opt to use C++, VB, Delphi, etc. to code an implementation.

A coclass is COM's language-independent notation for a class (a "class" in the object-oriented sense). Every COM object will be a coclass. When we create a COM object, we are in actual fact instantiating a COM coclass. What we get in return is the object but in the form of a reference to one of its interfaces. A coclass' declaration written in an IDL file or stored in a Type Library will specify the interfaces that are implemented by instances of that coclass. A simple and informal way to perceive a coclass is: a language-independent runtime class.

Both interfaces and coclasses are identified by GUIDs (an Interface ID or IID, for an interface, and a Class ID or CLSID, for a coclass). However, whereas an interface specification is generic and may be implemented by many coclasses, a coclass is unique. For every CLSID registered in a system, there will always be only one runtime class associated with it. The statement: "an implementation of a coclass", if it alludes to the implication that you can somehow register in a single OS two separate COM DLLs each of which contains the code for one coclass, does not make sense.

When we use a programming language to implement an interface, we do so by creating a coclass in which we provide the implementation. And a client instantiates this coclass to obtain that interface implementation.

For example, if we used VC++, our coclass will be in the form of a C++ class which derives from the pure virtual classes of the interfaces that our coclass is to contain. If we used VB, our coclass will take the form of a VB class module which declares (via the implements keyword) that it implements the coclass' interfaces.

A coclass, then, is often synonymous with compiled runtime code in which one or more COM interfaces are implemented. The compiled code being produced from one of the many object-oriented programming languages which supports COM.


With the general meaning of interfaces and coclasses squared away, let us move on with the coclass SimpleCOMObject1.

Listed below are the specs of the ISimpleCOMObject1 interface:

 [
  object,
  uuid(96A34C8B-E166-41EB-A390-9F9845F40D9F),
  dual,
  helpstring("ISimpleCOMObject1 Interface"),
  pointer_default(unique)
 ]
 interface ISimpleCOMObject1 : IDispatch
 {
  [id(1), helpstring("method Initialize")] HRESULT Initialize();
  [id(2), helpstring("method Uninitialize")] HRESULT Uninitialize();
  [id(3), helpstring("method DoLengthyFunction")] HRESULT
    DoLengthyFunction([in] long lTimeout);
 };

The ISimpleCOMObject1 interface stipulates a group of functionality characterized by an operation or procedure that takes a long time to complete. Interface ISimpleCOMObject1 is derived from IDispatch which means that it is dual-interfaced and can be invoked via Automation. This is very useful for Visual Basic Client Applications.

ISimpleCOMObject1 implementations are to supply an Initialize() function the job of which is to perform some kind of startup sequence which may include the creation of certain resources. Implementations must also supply an Uninitialize() method which is designed to allow the object to wind down and release resources where necessary.

Finally, we have DoLengthyFunction(). This function is specified to be asynchronous. That is, it is to return to the caller immediately after invocation. It will then perform some operation that takes a long time to complete. The "lTimeout" parameter is meant to be a time value (in milliseconds) that indicates to the object the timeout for this operation. If the operation was to last past this timeout, the object is supposed to terminate it. After completing this lengthy function or in the situation that timeout has elapsed, the object is to fire an event to its client.

The specs for the event set is listed below:

 [
  uuid(8B88E59A-4BB7-4DBC-819A-2E682845A4AE),
  helpstring("_ISimpleCOMObject1Events Interface")
 ]
 dispinterface _ISimpleCOMObject1Events
 {
  properties:
  methods:
  [id(1), helpstring("method LengthyFunctionCompleted")]
    HRESULT LengthyFunctionCompleted([in] long lStatus);
 };

The event set is an outgoing interface supported by the coclass SimpleCOMObject1 and which must be implemented by its client. There is only one method in this event set: LengthyFunctionCompleted(). This is fired when the SimpleCOMObject1 object has completed its time-consuming operation or when a timeout has elapsed before the operation has completed. The "lStatus" parameter is meant to indicate to the client the status of the operation.

In the example code, our COM coclass SimpleCOMObject1 fulfills the specification requirements for the interface ISimpleCOMObject1 but provides a very simple implementation. Details are listed in the next section.

The C++ Class CSimpleCOMObject1

The following points provide a summary of CSimpleCOMObject1.

  • CSimpleCOMObject1 is an STA COM Object.

    Note that a coclass in an IDL file describes a COM class but does not indicate what Apartment Model the implementation will adopt. The developer is free to choose whatever model deemed appropriate. Our C++ class CSimpleCOMObject1 codes coclass SimpleCOMObject1 as an STA object.

  • CSimpleCOMObject1 Uses a Separate Thread to Perform its Lengthy Operation.

    In order to allow its client to continue to function normally without blocking, CSimpleCOMObject1 uses a thread to perform its time-consuming operation when the DoLengthyFunction() is invoked. This thread is started when the Initialize() method is called and it is shutdown when Uninitialize() is executed.

    Note that in the interest of simplicity, we have designed CSimpleCOMObject1 as follows:

    1. It takes the timeout value supplied as the parameter to DoLengthyFunction() as the time taken for the so-called lengthy operation. Hence, if this timeout is 2000 (i.e., 2 seconds), CSimpleCOMObject1 is deemed to have completed its operation after 2 seconds and will fire its LengthyFunctionCompleted event immediately thereafter.
    2. Point 1 above simplifies our CSimpleCOMObject1 code, and the time-consuming operation is actually implemented as a simple call to the Sleep() API with the same timeout value.
    3. For simplicity, we do not handle the situation in which the DoLengthyFunction() method is invoked again but before the LengthyFunctionCompleted event has been fired first from a previous call to DoLengthyFunction().

    CSimpleCOMObject1, after all, is a simple illustrative example, not a professional quality product :-).

  • CSimpleCOMObject1 Fires its LengthyFunctionCompleted Event From The Thread.

    When CSimpleCOMObject1 fires the LengthyFunctionCompleted event, it does so inside the same thread in which it performs its lengthy function. This is most natural. But it also implies the following important points:

    1. CSimpleCOMObject1 must somehow obtain its client's _ISimpleCOMObject1Events event sink pointer and marshal it to the thread.
    2. Because the client's pointer to its _ISimpleCOMObject1Events event sink will be used inside the thread, this thread is deemed a COM thread and therefore must belong to an apartment.
    3. CSimpleCOMObject1 will use the CComThread helper class to manage this thread, hence this thread will enter an STA.
    4. Because this thread is an STA thread, if it is to export any of its own STA objects to other apartments, it must contain a message loop. This thread does not create nor export any of its own STA objects hence it does not need any message loop. However, I have included one both for illustrative purposes and for possible future use.

CSimpleCOMObject1's Implementation of ISimpleCOMObject1

This section provides an analysis of the methods of the ISimpleCOMObject1 interface as implemented by CSimpleCOMObject1. The ISimpleCOMObject1 interface consists of only three methods. The semantics of these methods have been clearly stated in the section "The Declaration of coclass SimpleCOMObject1". This section will pry open these methods and observe their codes in action.

CSimpleCOMObject1::Initialize()

STDMETHODIMP CSimpleCOMObject1::Initialize()
{
 // TODO: Add your implementation code here
 InitializeComThread();
 return S_OK;
}

The CSimpleCOMObject1::Initialize() method must be the first method to be called by a client. Its purpose is to set in motion the steps that eventually result in starting up a thread which is supposed to perform the time-consuming operation. This is done by CSimpleCOMObject1's internal InitializeComThread() function which is called inside Initialize().

The time-consuming thread is managed by CSimpleCOMObject1's member CComThread object named "m_COMThread":

protected :
 /* ***** Declare an instance of CComThread. ***** */
 CComThread   m_COMThread;

Let us analyze InitializeComThread() as listed below:

/* InitializeComThread() will initialize the CComThread object */
/* m_COMThread and connect it with our thread function         */
/* ThreadFunc_ComThread.                                       */
/* We will also use m_COMThread to marshall CSimpleCOMObject1's*/
/* sink pointers to the thread.                                */
void CSimpleCOMObject1::InitializeComThread()
{
  IUnknown* pIUnknown = NULL;
  /* Obtain this object's IUnknown pointer.               */
  /* This will be kept inside m_COMThread and will        */
  /* later be passed onto the ThreadFunc_ComThread thread.*/
  this -> QueryInterface (IID_IUnknown, (void**)&pIUnknown);
  if (pIUnknown)
  {
    /* Tell m_COMThread not to start the thread function   */
    /* ThreadFunc_ComThread immedietaly. Tell it to        */
    /* suspend until the ThreadResume() function is called.*/
    m_COMThread.SetFlags((CComThread::Flags)
      (CComThread::FLAG_START_SUSPENDED));
    /* Pass this object itself as a parameter to the thread*/
    /* function.                                           */
    m_COMThread.SetThreadParam ((LPVOID)this);
    m_COMThread.SetStartAddress (ThreadFunc_ComThread);
    m_COMThread.AddUnknownPointer(pIUnknown);
    /* We marshall this object's sink pointers */
    /* to the ThreadFunc_ComThread thread.     */
    MarshalEventDispatchInterfacesToComThread();
    m_COMThread.ThreadStart();
    m_COMThread.ThreadResume();
    pIUnknown -> Release();
    pIUnknown = NULL;
  }
}
  1. InitializeComThread() basically initializes CSimpleCOMObject1's CComThread object which is named "m_COMThread".
  2. It first calls QueryInterface() on itself to obtain its IUnknown interface pointer.
  3. This IUnknown interface pointer is later supplied to our user-supplied startup function ThreadFunc_ComThread() which will perform the time-consuming operation.
  4. The CComThread object m_COMThread is then instructed to suspend its thread until CComThread::ThreadResume() is called. This is done by calling CComThread::SetFlags() with flag FLAG_START_SUSPENDED.
  5. Note that this FLAG_START_SUSPENDED flag is not strictly required in our example but I have included its usage for example purposes.
  6. We then set a pointer to the current CSimpleCOMObject1 object itself (i.e., "this") as a CComThread "high-level" parameter. This is done by calling CComThread::SetThreadParam() with "this" as the parameter. This parameter is meant to be consumed by our startup function ThreadFunc_ComThread() as a back pointer to the CSimpleCOMObject1 object which started it in the first place.
  7. We will see later that ThreadFunc_ComThread() will call the CComThread::GetThreadParam() function to obtain this pointer to CSimpleCOMObject1.
  8. Next, ThreadFunc_ComThread() is set as the user-supplied startup function for CComThread.
  9. We then pass the current CSimpleCOMObject1's IUnknown interface pointer as an interface pointer to be marshaled to ThreadFunc_ComThread(). This is done by calling AddUnknownPointer().
  10. We shall see later that CSimpleCOMObject1's IUnknown interface pointer is not required for proper running of ThreadFunc_ComThread(). I have included the call to AddUnknownPointer() here for example usage purposes.
  11. Next, we call CSimpleCOMObject1::MarshalEventDispatchInterfacesToComThread(). We shall analyze this function in detail later. For now, it suffices to say that it gets hold of CSimpleCOMObject1's client's event sinks (for the _ISimpleCOMObject1Events event set) and calls CComThread::AddUnknownPointer() on each of them.
  12. This is done so that references to these event sinks are marshaled to the ThreadFunc_ComThread() function which will use them to fire _ISimpleCOMObject1Events events to a client application.
  13. The CComThread::ThreadStart() and CComThread::ThreadResume() are then called to get ThreadFunc_ComThread() up and going.

As expounded above, the ThreadFunc_ComThread() function is our user-supplied "thread" function which gets started by CComThread. We shall examine this function in detail later on.

We will first analyze the CSimpleCOMObject1::MarshalEventDispatchInterfacesToComThread() function and see how the event sinks of CSimpleCOMObject1's client can be retrieved and inserted into m_COMThread's stream vector. Through m_COMThread's stream vector, these event sinks will eventually get marshaled across to ThreadFunc_ComThread().

In order to follow the logic behind MarshalEventDispatchInterfacesToComThread() and understand how it deals with a client's event sinks, we must first study how connection points are implemented in CSimpleCOMObject1. CSimpleCOMObject1 is developed using ATL. All its connection point and connection point container codes are generated by ATL. We will therefore directly zoom in on how these constructs are implemented in ATL.

The ATL Connection Point Proxy

This sub-section does not intend to provide a complete exploration on ATL Connection Point and Connection Point Container Implementations. Several background information will be briefly explained. However, the spotlight will only be on the parts of the two topics that are relevant to retrieving and firing the event sinks of clients. I will assume that the reader is sufficiently knowledgeable of the basic principles of COM event firing. If a refresher is required, I recommend reading the CodeProject article: "Understanding COM Event Handling".

Let us now go back to the subject at hand. To help the COM object developer implement connection points and connection point containers, ATL provides the IConnectionPointImpl and IConnectionPointContainerImpl template classes. These classes greatly simplify the development work required for firing events from an ATL-based COM object.

The IConnectionPointContainerImpl template class is used by a base ATL class that wants to declare itself a connectable object. IConnectionPointContainerImpl implements the boiler-plate code for a connection point container to manage a collection of IConnectionPointImpl objects. It also provides default implementations for the methods of the IConnectionPointContainer interface based on the information that the ATL framework has accrued in the object's Connection Point Map (this is basically an array maintained in the object's source code between the macros BEGIN_CONNECTION_POINT_MAP, CONNECTION_POINT_ENTRY and END_CONNECTION_POINT_MAP).

Assuming that an ATL COM object supports an outgoing interface and a client application has registered its desire to receive events of this interface from the object, when the object is instantiated inside the client, one of the first things a client application will do is to QueryInterface() the object for its IConnectionPointContainer interface. After obtaining a reference to this interface, the client will typically call the FindConnectionPoint() method on this interface to obtain the IConnectionPoint interface of the source object of the event.

Using our current example SimpleCOMObject1 COM object and the VBTest client program, we can see this in action by placing a breakpoint in the IConnectionPointContainerImpl::FindConnectionPoint() method. This method can be located in the atlcom.h header file.

Compile both the SimpleCOMObject1 and VBTest projects. Point the "Executable for debug session" program at VBTest.exe. Start a debug session from the SimpleCOMObject1 project. A screenshot is provided below:

Image 5

Notice from the Call Stack window that the call to FindConnectionPoint() is a result of some action inside the Form_Load event of the VB application's FormMain window. A screenshot of this event is displayed below:

Image 6

As can be seen from the above diagram, the code in FormMain::Form_Load() is actually a call to instantiate SimpleCOMObject1. Hence the call to FindConnectionPoint() is an early action within the VB Engine to search for the _ISimpleCOMObject1Events connection point within the SimpleCOMObject1 object as it is being created.

After obtaining a reference to the IConnectionPoint interface of the source object within SimpleCOMObject1, we will find that the IConnectionPointImpl::Advise() method will be called on that reference. Please refer to the diagram below:

Image 7

This call to Advise() is still within the call to the instantiation of SimpleCOMObject1 within the Form_Load() event of the FormMain window. It signals to the SimpleCOMObject1 object that the client wants to firmly establish the event connection relationship with the object.

We can tell what connection point the client is trying to connect with by observing the out value of the "iid" local variable after the call to GetConnectionInterface(). This is actually DIID__ISimpleCOMObject1Events.

The input "pUnkSink" is the pointer to the IUnknown interface of the client's sink somewhere within the VB app.

I have listed the full code of IConnectionPointImpl::Advise() below. Please pay attention to the next set of statements after GetConnectionInterface():

template <class T, const IID* piid, class CDV>
STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(IUnknown* pUnkSink,
 DWORD* pdwCookie)
{
 T* pT = static_cast<T*>(this);
 IUnknown* p;
 HRESULT hRes = S_OK;
 if (pUnkSink == NULL || pdwCookie == NULL)
  return E_POINTER;
 IID iid;
 GetConnectionInterface(&iid);
 hRes = pUnkSink->QueryInterface(iid, (void**)&p);
 if (SUCCEEDED(hRes))
 {
  pT->Lock();
  *pdwCookie = m_vec.Add(p);
  hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
  pT->Unlock();
  if (hRes != S_OK)
   p->Release();
 }
 else if (hRes == E_NOINTERFACE)
  hRes = CONNECT_E_CANNOTCONNECT;
 if (FAILED(hRes))
  *pdwCookie = 0;
 return hRes;
}

The call to QueryInterface() on the input pUnkSink is Advise() function's attempt to retrieve a reference to the event sink's interface. In our case, this will be a reference to the DIID__ISimpleCOMObject1Events interface of the sink. This will be stored inside "p". Note that immediately after that, we add "p" to a IConnectionPointImpl member object named m_vec.

IConnectionPointImpl::m_vec is an object of type CComDynamicUnkArray. It is meant to be a dynamic array of pointers to one or more client sinks, each sink being an implementation of the _ISimpleCOMObject1Events event interface on the client application. m_vec is filled at runtime whenever IConnectionPointImpl::Advise() is called.

Notice some diversion from normal ATL implementation policy. CSimpleCOMObject1, being a connection point container, directly inherits from IConnectionPointContainerImpl. However, despite the fact that it implements the connection point for _ISimpleCOMObject1Events, CSimpleCOMObject1 does not directly derive from IConnectionPointImpl. Instead, it uses a base class named CProxy_ISimpleCOMObject1Events.

CProxy_ISimpleCOMObject1Events is a connection point helper class that contains the code for allowing a client to register its _ISimpleCOMObject1Events event sink with an instance of this class. It also implements all the the necessary event firing helper functions.

CSimpleCOMObject1 uses CProxy_ISimpleCOMObject1Events as a base class. This makes CSimpleCOMObject1 inherit all the connection point helper data and functions for the _ISimpleCOMObject1Events outgoing interface (including m_vec).

Why does ATL generate a separate proxy class instead of generating the event firing code within CSimpleCOMObject1 itself?

The answer is re-use. IConnectionPointImpl contains the boiler-plate code for the implementation of the methods of an IConnectionPoint interface for a connectable object. IConnectionPointImpl itself is re-usable across the C++ classes of all connectable objects. IConnectionPointImpl can be thought of as a specialized implementation of a connection point for a particular event interface and a particular connection point container. However, IConnectionPointImpl falls short of being able to provide any code to actually invoke the event methods of the event interface that it represents. This is because the methods of an event interface are not predictable in advance.

In comes Connection Point Proxies. A connection point proxy is a C++ template class derived from IConnectionPointImpl. The value of a proxy is its event firing helper functions. These are functions which contain code that performs the actual event firing from a connectable object to its client's or clients' event sink(s). A connection point proxy can be thought of as a further specialized implementation of IConnectionPointImpl with event firing helper functions.

Now, just like IConnectionPointImpl, a connection point proxy, itself a C++ template class, can be re-used across connectable objects that support a particular event interface. For instance, if two ATL classes (CClassA and CClassB, say) support the outgoing interface named _ISimpleCOMObject1Events, then both classes can be derived from CProxy_ISimpleCOMObject1Events, albeit the individual C++ class names must be used as template parameters (e.g., CProxy_ISimpleCOMObject1Events<CClassA> and CProxy_ISimpleCOMObject1Events<CClassB>).

Let us now return to the original point of this side-note, which is to determine how an ATL object retrieves and fires its client's event sinks.

Connectable objects developed using ATL use Connection Point Proxies to simplify their implementation of connection points. Using these proxies, an ATL object can freely access the event sinks of its clients via the m_vec member data and use it to fire events. It can also use m_vec to access the event sinks for any internal customized operations (e.g., marshal these event sink interfaces to private threads).

MarshalEventDispatchInterfacesToComThread()

Now that we have explored how an ATL COM object accesses the event sinks of its clients, we can proceed to explore how CSimpleCOMObject1 marshals the event sinks of its client to its CComThread object "m_COMThread". This is done in the function MarshalEventDispatchInterfacesToComThread(). Having understood the meaning and place of "m_vec", the code in this function is now simpler and more easily understood:

/* The pointers to this object's client event sinks are      */
/* kept in IConnectionPointImpl::m_vec which is a collection */
/* of IUnknown pointers.                             */
void CSimpleCOMObject1::MarshalEventDispatchInterfacesToComThread()
{
  int   nConnections = m_vec.GetSize();
  int   nConnectionIndex = 0;
  HRESULT  hrTemp = S_OK;
  /* Go through each and every IUnknown pointers in m_vec and */
  /* store each pointer in m_COMThread.                       */
  for (nConnectionIndex = 0;
       nConnectionIndex < nConnections;
       nConnectionIndex++)
  {
    Lock();
    CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
    Unlock();
    IUnknown* pIUnknownTemp = reinterpret_cast<IUnknown*>(sp.p); 
    if (pIUnknownTemp)
    {
      /* Also no need to call pIUnknownTemp->Release().*/
      /* pIUnknownTemp is a temporary pointer          */
      /* to the IUnknown pointer in sp. And sp will    */
      /* automatically call Release() on its           */
      /* internal IUnknown pointer.                    */
      m_COMThread.AddUnknownPointer(pIUnknownTemp);
    }
  }
}

MarshalEventDispatchInterfacesToComThread() uses CSimpleCOMObject1's inherited "m_vec" member to iterate through all its client's event sinks. Each event sink is gotten via the CComDynamicUnkArray::GetAt() function. The return value is an IUnknown pointer. Each IUnknown pointer is then passed as parameter to the CComThread::AddUnknownPointer() function.

CSimpleCOMObject1::Uninitialize()

STDMETHODIMP CSimpleCOMObject1::Uninitialize()
{
 // TODO: Add your implementation code here
 UninitializeComThread();
 return S_OK;
}
void CSimpleCOMObject1::UninitializeComThread()
{
 if (m_hExitThread)
 {
   SetEvent(m_hExitThread);
 }
 m_COMThread.WaitThreadStop();
}

The CSimpleCOMObject1::Uninitialize() method is the last method to be called by a client. When Uninitialize() is called, CSimpleCOMObject1's internal UninitializeComThread() function is invoked. UninitializeComThread() essentially signals to the user-specified ThreadFunc_ComThread() thread (that it passed to CComThread) to terminate. This is done by setting the CSimpleCOMObject1 member event object "m_hExitThread". This event object is shared between CSimpleCOMObject1 and the ThreadFunc_ComThread() thread. Upon setting this event object, ThreadFunc_ComThread() begins to wind down and eventually exits.

After setting m_hExitThread, UninitializeComThread() calls m_COMThread's WaitThreadStop() function. This causes the current thread (in which UninitializeComThread() is called) to block until the ThreadFunc_ComThread() thread terminates completely. As was explained in an earlier section on CComThread::WaitThreadStop(), the current thread, which is actually the UI thread created by the Visual Basic test application, will continue to function properly as its message loop continues to be serviced while waiting for the ThreadFunc_ComThread() thread to complete.

CSimpleCOMObject1::DoLengthyFunction()

STDMETHODIMP CSimpleCOMObject1::DoLengthyFunction(long lTimeout)
{
 // TODO: Add your implementation code here
 m_lLengthyFunctionTimeout = lTimeout;
 if (m_hStartLengthyFunction)
 {
   SetEvent(m_hStartLengthyFunction);
 }
 return S_OK;
}

The CSimpleCOMObject1::DoLengthyFunction() function is called by a client application to signal to the SimpleCOMObject1 coclass object to start its time-consuming operation function. This is done by setting the member event object m_hStartLengthyFunction. This event object is shared with the ThreadFunc_ComThread() thread. Once this event is set, the ThreadFunc_ComThread() thread will begin its time-consuming operation.

The parameter to DoLengthyFunction() is a long value which indicates the timeout for the time-consuming operation. This timeout value is saved in CSimpleCOMObject1::m_lLengthyFunctionTimeout. This member long variable will also be used by the ThreadFunc_ComThread() thread.

How CSimpleCOMObject1 Fires its Event from an External Thread

We have reached the climatic point of this entire section to demonstrate an advanced STA application. The previous sub-sections have provided general background information to the application and have served as a buildup towards this final point: which is to demonstrate the firing of an STA COM object's events to its client through a thread external from the one in which the object itself was created (i.e., from an external apartment).

It is now time for us to study the ThreadFunc_ComThread() function.

ThreadFunc_ComThread()

DWORD WINAPI ThreadFunc_ComThread(LPVOID lpThreadParameter)
{
  CComThread*  pCComThread
               = (CComThread*)lpThreadParameter;
  CSimpleCOMObject1* pCSimpleCOMObject1
               = (CSimpleCOMObject1*)(pCComThread -> GetThreadParam());
  HANDLE       dwChangeHandles[2];
  bool         bContinueLoop = true;
  IUNKNOWN_VECTOR           theVector;
  IUNKNOWN_VECTOR::iterator theIterator;
  _ISimpleCOMObject1Events* p_ISimpleCOMObject1Events = NULL;
  DWORD                     dwWaitStatus = 0;
  DWORD                     dwRet = 0;
  dwChangeHandles[0] = pCSimpleCOMObject1 -> m_hExitThread;
  dwChangeHandles[1] = pCSimpleCOMObject1 -> m_hStartLengthyFunction;
  // We go through the IUnknown pointers stored in pCComThread
  // (by the object which called this thread) and search for
  // a _ISimpleCOMObject1Events dispinterface pointer.
  // If found, this _ISimpleCOMObject1Events dispinterface pointer
  // is the event handler interface of the client that uses
  // the CSimpleCOMObject1 COM Object.
  theVector = (IUNKNOWN_VECTOR&)(*pCComThread);
  for (theIterator = theVector.begin();
       theIterator != theVector.end();
       theIterator++)
  {
     IUnknown* pIUnknownTemp = (*theIterator);
     IDispatch* pIDispatch = NULL;
     if (pIUnknownTemp)
     {
       pIUnknownTemp -> QueryInterface
       (IID_IDispatch, (void**)&pIDispatch);
     }
     if(pIDispatch)
     {
       pIDispatch -> QueryInterface
       (
         __uuidof(_ISimpleCOMObject1Events),
         (void**)&p_ISimpleCOMObject1Events
       );
       pIDispatch -> Release();
       pIDispatch = NULL;
       if (p_ISimpleCOMObject1Events)
       {
         break;
       }
     }
  }
  // Note that p_ISimpleCOMObject1Events may be NULL.
  // This will be so if the client does not wish to receive
  // any event from the object.
  // Msg Loop while waiting for thread to exit.
  while (bContinueLoop)
  {
    // Wait for notification.
    dwWaitStatus = ::MsgWaitForMultipleObjectsEx
    (
      (DWORD)2,
      dwChangeHandles,
      (DWORD)INFINITE,
      (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE),
      (DWORD)(MWMO_INPUTAVAILABLE)
    );
    switch (dwWaitStatus)
    {
      // First wait (thread exit) object has been signalled.
      case WAIT_OBJECT_0 :
      {
         // Flag to indicate stop loop.
         bContinueLoop = false;
         break;
      }
      // Second wait (do lengthy function) object has been signalled.
      case WAIT_OBJECT_0 + 1:
      {
         // We must now perform the lengthy function.
         Sleep(pCSimpleCOMObject1 -> m_lLengthyFunctionTimeout);       
         // When capturing has completed,
         // we fire the LengthyFunctionCompleted() event.
         if (p_ISimpleCOMObject1Events)
         {
           IDispatch* pIDispatch = NULL;
           p_ISimpleCOMObject1Events -> QueryInterface(&pIDispatch);
           if (pIDispatch)
           {
             CComVariant  varResult;
             CComVariant* pvars = new CComVariant[1];
             VariantClear(&varResult);
             pvars[0] = (long)0;
             DISPPARAMS disp = { pvars, NULL, 1, 0 };
             pIDispatch->Invoke
             (
               0x1,
               IID_NULL,
               LOCALE_USER_DEFAULT,
               DISPATCH_METHOD,
               &disp,
               &varResult,
               NULL,
               NULL
             );
             pIDispatch -> Release();
             pIDispatch = NULL;
             delete[] pvars;
           }
         }
         ResetEvent(dwChangeHandles[1]);
         break;
       }
       // Windows message has arrived.
       case WAIT_OBJECT_0 + 2:
       {
         MSG msg;
         // Dispatch all windows messages in queue.
         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
           TranslateMessage (&msg);
           DispatchMessage(&msg);
         }
         break;
       }
       default:
       {
         break;
       }
     }
   }
   if (p_ISimpleCOMObject1Events)
   {
     p_ISimpleCOMObject1Events -> Release();
     p_ISimpleCOMObject1Events = NULL;
   }
   return dwRet;
}

The ThreadFunc_ComThread() function is the workhorse of the CSimpleCOMObject1 class. Its main objective is to perform the so-called time consuming operation and then fire the LengthyFunctionCompleted event to the client application.

ThreadFunc_ComThread() and CComThread::WaitThreadStop() use a common principle which will be expounded in detail when we study the ThreadMsgWaitForSingleObject() function later on. All three functions centre their algorithms around a call to the MsgWaitForMultipleObjectsEx() API and will service Windows messages which may be posted or sent to their threads while waiting on object handles to be signaled. We will explore all these later on.

For now, let's study the ThreadFunc_ComThread() function in greater detail.

Synopsis

  1. ThreadFunc_ComThread() is the user-defined startup function supplied by CSimpleCOMObject1 to its CComThread object via the CComThread::SetStartAddress() function.
  2. It shares two event handles with the CSimpleCOMObject1 class: m_hExitThread and m_hStartLengthyFunction both of which are member objects of CSimpleCOMObject1.
  3. CSimpleCOMObject1::m_hExitThread is used to signal to ThreadFunc_ComThread() to exit.
  4. CSimpleCOMObject1::m_hStartLengthyFunction is used to signal to ThreadFunc_ComThread() to start its time-consuming operation.
  5. Early in its life, ThreadFunc_ComThread() goes through all the unmarshaled IUnknown interface pointers contained within its managing CComThread object in a bid to find the _ISimpleCOMObject1Events event sink pointer of CSimpleCOMObject1's client.
  6. These unmarshaled IUnknown interface pointers were originally passed to the CComThread object via the AddUnknownPointer() function called from the thread that owned the CComThread object itself.
  7. As was seen in the section CSimpleCOMObject1::Initialize() and MarshalEventDispatchInterfacesToComThread(), we know that a pointer to the client's _ISimpleCOMObject1Events event sink was passed to CSimpleCOMObject1's CComThread object via AddUnknownPointer() in order to marshal this event sink pointer to ThreadFunc_ComThread().
  8. Once ThreadFunc_ComThread() gets this event sink pointer, it enters a while loop that uses MsgWaitForMultipleObjectsEx() to wait on the m_hExitThread and m_hStartLengthyFunction event objects.
  9. ThreadFunc_ComThread() also provides a message loop to process any Windows messages that may be posted or sent to it. This is not strictly necessary as ThreadFunc_ComThread() does not export any interface pointers. It does not even create any objects. I have, however, inserted the message loop for possible future use.
  10. If the m_hExitThread event is set, ThreadFunc_ComThread() begins to wind down and eventually exits.
  11. If the m_hStartLengthyFunction event is set, ThreadFunc_ComThread() fires the LengthyFunctionCompleted event of the client's _ISimpleCOMObject1Events event sink.

Let us now study ThreadFunc_ComThread() in greater detail:

  1. The parameter to ThreadFunc_ComThread() is actually a pointer to its managing CComThread object. This is stored in the local variable "pCComThread" after casting the input parameter which is an LPVOID.
  2. Having gotten a pointer to the thread function's managing CComThread object (now stored in pCComThread), we use it to call the GetThreadParam() function to obtain the higher-level parameter to ThreadFunc_ComThread(). This high-level parameter is actually a pointer to the CSimpleCOMObject1 object which owns the CComThread object which manages ThreadFunc_ComThread(). This is stored in the local variable "pCSimpleCOMObject1".
  3. ThreadFunc_ComThread() also defines a local array of HANDLEs ("dwChangeHandles") which stores the handles of objects which we want to later pass to a call to MsgWaitForMultipleObjectsEx().
  4. A local boolean variable "bContinueLoop" is used to control the while loop which contains the call to MsgWaitForMultipleObjectsEx().
  5. A local vector "theVector" of IUnknown pointers (type IUNKNOWN_VECTOR) is used to refer to the corresponding vector of IUnknown interface pointers contained in the CComThread object which manages ThreadFunc_ComThread().
  6. This vector of IUnknown pointers is a collection of interface pointers which are actually unmarshaled proxies of interface pointers from the thread that owns the CComThread object.
  7. ThreadFunc_ComThread() sets the values of the elements of the dwChangeHandles array to the m_hExitThread and m_hStartLengthyFunction event handles which actually belong to the CSimpleCOMObject1 object that pCSimpleCOMObject1 refers to.
  8. theVector is then set to the corresponding vector contained within the managing CComThread object.
  9. ThreadFunc_ComThread() then goes through each IUnknown pointer contained inside theVector, and QueryInterfaces it to see whether it supports both IDispatch and DIID__ISimpleCOMObject1Events interfaces. Once it finds one, it stores it inside a local _ISimpleCOMObject1Events interface pointer "p_ISimpleCOMObject1Events".
  10. This p_ISimpleCOMObject1Events will be Release()'d at the end of the thread function.
  11. Note that for practical purposes, ThreadFunc_ComThread() may not find such an interface pointer. This will be the case if the client application does not supply any event handlers for the _ISimpleCOMObject1Events events.
  12. ThreadFunc_ComThread() then enters a while loop (with "bContinueLoop" being the control variable) which centers around a call to the MsgWaitForMultipleObjectsEx() API.
  13. The return value of the MsgWaitForMultipleObjectsEx() API call is stored inside the local DWORD variable named "dwWaitStatus". This local variable determines what happens inside the while loop.
  14. If dwWaitStatus equals WAIT_OBJECT_0, it means that m_hExitThread is signaled. This effectively means that ThreadFunc_ComThread() is to terminate. This is indeed so. Note that bContinueLoop is set to false so that when the top of the while loop is reached again, the while loop is not repeated.
  15. If dwWaitStatus equals WAIT_OBJECT_0 + 1, it means that m_hStartLengthyFunction is signaled. This effectively means that ThreadFunc_ComThread() is to begin its so-called time-consuming operation.
  16. The Sleep() API is used to simulate the time-consuming operation. The timeout for the Sleep() API is the same value as the parameter for the DoLengthyFunction() function which started the whole ThreadFunc_ComThread() thread.
  17. Recall that this parameter to DoLengthyFunction() is saved in CSimpleCOMObject1::m_lLengthyFunctionTimeout. We use this member variable as the timeout parameter for Sleep().
  18. After Sleep() is called, we will proceed to fire the LengthyFunctionCompleted event of the client's _ISimpleCOMObject1Events event sink.
  19. We do this by first checking whether p_ISimpleCOMObject1Events is non-NULL. If so, we QueryInterface it for its IDispatch interface. Note that the _ISimpleCOMObject1Events event interface is dispinterface-based. Hence the event methods can only be called via IDispatch::Invoke(). This is the reason why we must QueryInterface p_ISimpleCOMObject1Events for its IDispatch interface.
  20. What happens next is typical IDispatch::Invoke() call sequence. The Invoke() method is executed on the IDispatch interface pointer retrieved from p_ISimpleCOMObject1Events.
  21. Notice that we supplied 0x01 as the dispatch ID of the method (of the _ISimpleCOMObject1Events dispinterface) to invoke. This matches the dispatch ID of LengthyFunctionCompleted (see the IDL definition of _ISimpleCOMObject1Events). Note also that a value of zero will be passed as the event method parameter (pvars[0] = (long)0;).
  22. After using the IDispatch interface pointer to invoke the event, we Release() it.
  23. We will also ResetEvent() the m_hStartLengthyFunction event handle so that it can be re-used.
  24. Now if dwWaitStatus equals WAIT_OBJECT_0 + 2, it means that a Windows message is received by MsgWaitForMultipleObjectsEx(). We service the message by a message loop.

In the context of our VBTest client application, when ThreadFunc_ComThread() invokes the event method of its client's _ISimpleCOMObject1Events event sink (as in step 20 above), the SimpleCOMObject1Obj_LengthyFunctionCompleted() event handler function (written in Visual Basic) will be called. This event handler is listed below:

VB
Private Sub SimpleCOMObject1Obj_LengthyFunctionCompleted _
                                    (ByVal lStatus As Long)
  Dim strMessage As String

  strMessage = "Lengthy Function Completed. Status : " & Str$(lStatus)

  MsgBox strMessage
End Sub

A message box will be displayed showing the lStatus value (zero) passed from ThreadFunc_ComThread() (as in step 21 above).

If you run VBTest while debugging the SimpleCOMObject1 project, you can put a breakpoint in the VB event handler function and observe it being called at runtime. Via the VC++ debugger, you can also observe the ID of the thread which is executing when the VB event handler function is called. This will not be the thread ID of ThreadFunc_ComThread().

This is because the _ISimpleCOMObject1Events event sink in the VBTest application is an STA object which lives in the same STA as the application's UI thread (this STA is also the same apartment used by the application's SimpleCOMObject1 object).

Summary

Let us now re-iterate the steps taken to ensure the possibility of safely firing the methods of an event interface from a thread external to the one in which an object was instantiated. And since Single-Threaded Apartments are used throughout our demonstration, we are actually firing the events of an STA object from an external STA. The following is a summary:

  1. CSimpleCOMObject1's CComThread object "m_COMThread" is used to manage the user-defined thread function.
  2. The CComThread::SetStartAddress() function is used to indicate to CComThread the user-defined thread function to manage. This is ThreadFunc_ComThread().
  3. The CComThread::SetThreadParam() function is called (with a pointer to CSimpleCOMObject1) to allow ThreadFunc_ComThread() to interact with some shared member data of CSimpleCOMObject1.
  4. The CComThread::AddUnknownPointer() function is used to marshal the event sinks of CSimpleCOMObject1's client to ThreadFunc_ComThread().
  5. Now because CSimpleCOMObject1 is written in ATL, we can access this object's client's event sinks via CSimpleCOMObject1's appropriate Connection Point Proxy. In the case of the _ISimpleCOMObject1Events event interface, this will be CProxy_ISimpleCOMObject1Events.
  6. The Connection Point Proxy's dynamic array of client sink pointers is used to access the client sink pointers. This dynamic array is CProxy_ISimpleCOMObject1Events::m_vec.
  7. Just before ThreadFunc_ComThread() begins life, all the interface pointers marshaled from CSimpleCOMObject1's own STA is unmarshaled to ThreadFunc_ComThread()'s STA.
  8. ThreadFunc_ComThread() uses its managing CComThread's IUNKNOWN_VECTOR cast operator to access all the unmarshaled interface pointers.
  9. ThreadFunc_ComThread() will iterate through all the unmarshaled interface pointers looking for a pointer to the client's _ISimpleCOMObject1Events event sink.
  10. When the appropriate time comes to fire the event methods of the _ISimpleCOMObject1Events event sink, the normal IDispatch method invocation techniques are used.

This generally concludes our in-depth study of the advanced STA application.

The ThreadMsgWaitForSingleObject() Function

This section gives a proper account of the utility function we first met in part one: ThreadMsgWaitForSingleObject(). The code for this function can be found in the "Shared\ComThread.cpp" source file contained in the source codes ZIP file which accompanies this article.

The concepts behind this cool utility is important as it permits a user-interface thread to continue servicing its message pump while waiting on an object handle to be signaled. It is centered around a call to the workhorse MsgWaitForMultipleObjectsEx() Win32 API.

I have already made references to ThreadMsgWaitForSingleObject() in an earlier section which talked about the CComThread::WaitThreadStop() function. The code for the ThreadFunc_ComThread() thread function also made use of the MsgWaitForMultipleObjectsEx() API and the exact same thread-blocking mechanisms suitable for User-Interface threads. We are therefore familiar with what ThreadMsgWaitForSingleObject() intends to achieve.

Let us now formally examine this function in more detail:

DWORD ThreadMsgWaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds)
{
  HANDLE dwChangeHandles[1] = { hHandle };
  DWORD  dwWaitStatus = 0;
  DWORD  dwRet = 0;
  bool  bContinueLoop = true;
  /* Msg Loop while waiting for hHandle to be signaled.*/
  while (bContinueLoop)
  {
    /* Wait for notification.*/
    dwWaitStatus = ::MsgWaitForMultipleObjectsEx
    (
      (DWORD)1,         
      dwChangeHandles,
      (DWORD)dwMilliseconds, 
      (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE),     
      (DWORD)(MWMO_INPUTAVAILABLE)         
    );
    switch (dwWaitStatus)
    {
      /* First wait (hHandle) object has been signalled.*/
      case WAIT_OBJECT_0 :
      {
        dwRet = dwWaitStatus;
        /* Flag to indicate stop loop.*/
        bContinueLoop = false;
        break;
      }
      /* Windows message has arrived.*/
      case WAIT_OBJECT_0 + 1:
      {
        MSG msg;
        /* Dispatch all windows messages in queue.*/
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
          TranslateMessage (&msg);
          DispatchMessage(&msg);
        }
        break;
      }
      /* Timeout has elapsed.*/
      case WAIT_TIMEOUT :
      {
        dwRet = dwWaitStatus;
        /* Flag to indicate stop loop.*/
        bContinueLoop = false;
        break;
      }
      default:
      {
        break;
      }
    }
  }
  return dwRet;
}

Synopsis

  1. ThreadMsgWaitForSingleObject() is essentially a loop that centers around a call to the MsgWaitForMultipleObjectsEx() Win32 API.
  2. The parameters to the MsgWaitForMultipleObjectsEx() call is such that it will block until either an input object handle has been signaled, or until timeout has elapsed, or until a Windows message is received on the current thread from which MsgWaitForMultipleObjectsEx() was called (this will also be the same thread that called ThreadMsgWaitForSingleObject()).
  3. If MsgWaitForMultipleObjectsEx() returned because the input object handle has been signaled, ThreadMsgWaitForSingleObject() returns.
  4. If MsgWaitForMultipleObjectsEx() returned because timeout has elapsed, ThreadMsgWaitForSingleObject() returns.
  5. If MsgWaitForMultipleObjectsEx() returned because a Windows message is received, the message is processed and dispatched to the appropriate Windows procedure. After that, the loop is repeated and MsgWaitForMultipleObjectsEx() is called once again to wait for either the signaling of the input object handle or the receipt of a Windows message.

Let us now examine the code of this function line by line:

  1. ThreadMsgWaitForSingleObject() defines an array ("dwChangeHandles") of just one HANDLE value. The single element of this array is set to an object handle which is supplied as the first parameter to the ThreadMsgWaitForSingleObject() function (i.e., "hHandle").
  2. dwChangeHandles will be used as the array of object handles that MsgWaitForMultipleObjectsEx() will wait on.
  3. ThreadMsgWaitForSingleObject() also uses a local variable named "bContinueLoop" to control the circulation of a while loop.
  4. The body of the while loop makes a call to the MsgWaitForMultipleObjectsEx() API.
  5. The use of the QS_ALLINPUT flag combined with the QS_ALLPOSTMESSAGE flag ensures that all messages sent or posted to the current thread (which called ThreadMsgWaitForSingleObject()) will cause the MsgWaitForMultipleObjectsEx() function to return.
  6. Please see the MSDN documentation for more details on the MsgWaitForMultipleObjectsEx() function.
  7. Now, when MsgWaitForMultipleObjectsEx() returns, its return value is captured inside the local variable "dwWaitStatus".
  8. If "dwWaitStatus" equals WAIT_OBJECT_0, it means that the first object in the dwChangeHandles array that MsgWaitForMultipleObjectsEx() is waiting on has been signaled.
  9. This effectively means that the object behind hHandle has been signaled. The "bContinueLoop" local variable is set to false and the while loop is broken out of. Once this happens, ThreadMsgWaitForSingleObject() returns with a value of WAIT_OBJECT_0.
  10. If "dwWaitStatus" equals WAIT_OBJECT_0 + 1, it means that a Windows message has arrived for the current thread (that called ThreadMsgWaitForSingleObject()).
  11. In this situation, ThreadMsgWaitForSingleObject() will enter an internal message loop to process all the messages in the thread's message queue.
  12. Note well that the MsgWaitForMultipleObjectsEx() API must not be misinterpreted as being able to internally process Windows messages for us. It will only return when a Windows message comes. We must internally process the Windows message ourselves.
  13. Once all messages have been properly dispatched, control must flow back to the top of the outer while loop, the "bContinueLoop" control variable is still set to true at this time and so the while loop will continue to function.
  14. The MsgWaitForMultipleObjectsEx() API must be called once again to repeat the same cycle of waiting on the signaled state of the object behind hHandle while processing Windows messages for the current thread when any arrives.
  15. If "dwWaitStatus" equals WAIT_TIMEOUT, it means that timeout has elapsed (the timeout period being that specified as the second parameter to ThreadMsgWaitForSingleObject()).
  16. In this case, the "bContinueLoop" local variable is set to false and the while loop is broken out of. ThreadMsgWaitForSingleObject() returns with a value of WAIT_TIMEOUT.

ThreadMsgWaitForSingleObject()'s similarity to the CComThread::WaitThreadStop() function should be clear by now:

  • Both ThreadMsgWaitForSingleObject() and WaitThreadStop() will block until some object handle is in a signaled state. However, ThreadMsgWaitForSingleObject() is generic and will wait on any object handle (supplied as an input parameter) while WaitThreadStop() specifically waits for the CComThread thread to terminate.
  • While waiting on their respective objects, both ThreadMsgWaitForSingleObject() and WaitThreadStop() will service Windows messages which arrive for their respective owner threads.

The only dissimilarity between these two functions lies in the fact that ThreadMsgWaitForSingleObject() will generically block for a specified length of time (including INFINITE) whereas WaitThreadStop() will wait infinitely for the CComThread thread to terminate.

The implementation codes of ThreadMsgWaitForSingleObject() and the while loop in ThreadFunc_ComThread() also have many parallels except that ThreadFunc_ComThread() uses MsgWaitForMultipleObjectsEx() to wait on two object handles.

In Conclusion

I certainly hope that you have benefited from our long and thorough dissertation into the world of the COM Single-Threaded Apartment and Marshaling. Many CodeProject readers have been very kind to me and posted good reviews for Part 1. I truly appreciate your encouragements and sincerely hope that Part 2 here has lived up to expectations. Please post a message to me if you discover any errors in this article or if you have any good suggestions on how to improve it further.

I started out Part 2 ambitiously with a desire to provide an additional advanced STA example other than the one which was presented above. This second advanced STA example was to demonstrate the use of the GIT. I had also wanted to extend the ThreadMsgWaitForSingleObject() function with a multiple-object version (ThreadMsgWaitForMultipleObjects()). However, I eventually dropped both ideas as this article was getting too long. I also did not want to further delay publishing it.

The ThreadMsgWaitForMultipleObjects() function, nevertheless, remains a good idea, and I hope to eventually code a sample implementation and provide a small article documenting it. I also want to start researching into the other Apartment Models, especially the MTA, to gather material for a possible next article.

References

  • The Essence of COM, A Programmer's Workbook (3rd Edition) by David S. Platt. Published by Prentice Hall PTR.
  • Inside COM by Dale Rogerson. Published by Microsoft Press.
  • Essential COM by Don Box. Published by Addison-Wesley.

Update History

February 19th 2005

  • Discovered and resolved bug in ThreadMsgWaitForSingleObject():
    • The switch statement in ThreadMsgWaitForSingleObject() did not handle the case where dwWaitStatus equals WAIT_TIMEOUT.
    • Documentation for WAIT_TIMEOUT case has been provided and source code ZIP file updated.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer NEC
Singapore Singapore
Lim Bio Liong is a Specialist at a leading Software House in Singapore.

Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.

Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area.

Comments and Discussions

 
QuestionTesting the behaviour of sink interface without marshalling Pin
Member 1266135514-Sep-23 21:08
Member 1266135514-Sep-23 21:08 
QuestionTrigger COM Object Events for Clients Written Using the C ++ Language Pin
osgtub21-Oct-19 16:21
osgtub21-Oct-19 16:21 
QuestionNo Release on IUnknown pointer Pin
Iwan Limpo3-Dec-13 19:06
Iwan Limpo3-Dec-13 19:06 
GeneralProviding user-definable timeouts for DCOM I/O Pin
fesader13-Sep-10 2:35
fesader13-Sep-10 2:35 
GeneralMy vote of 5 Pin
fesader13-Sep-10 2:31
fesader13-Sep-10 2:31 
QuestionWhich way to use marshal/unmarshal across processes? Pin
ehaerim7-Mar-09 12:11
ehaerim7-Mar-09 12:11 
AnswerRe: Which way to use marshal/unmarshal across processes? Pin
Member 1096351122-Jul-14 4:13
Member 1096351122-Jul-14 4:13 
QuestionMultiple thread (having same COINIT_MULTITHREADED apartment) accessing a STA object Pin
stormydaniels18-Nov-08 23:30
stormydaniels18-Nov-08 23:30 
AnswerRe: Multiple thread (having same COINIT_MULTITHREADED apartment) accessing a STA object Pin
Lim Bio Liong19-Nov-08 22:33
Lim Bio Liong19-Nov-08 22:33 
GeneralRe: Multiple thread (having same COINIT_MULTITHREADED apartment) accessing a STA object Pin
stormydaniels24-Nov-08 15:59
stormydaniels24-Nov-08 15:59 
GeneralRe: Multiple thread (having same COINIT_MULTITHREADED apartment) accessing a STA object Pin
Lim Bio Liong24-Nov-08 20:01
Lim Bio Liong24-Nov-08 20:01 
GeneralRe: Multiple thread (having same COINIT_MULTITHREADED apartment) accessing a STA object Pin
Lim Bio Liong24-Nov-08 20:02
Lim Bio Liong24-Nov-08 20:02 
Generalconfused about the statement of GIT support for marshall proxy interface Pin
George_George8-Aug-08 23:19
George_George8-Aug-08 23:19 
Questionwhat type of project did you create for this code Pin
dbrower25619-Jul-08 2:01
dbrower25619-Jul-08 2:01 
Questionhow to do a step through Pin
dbrower25628-May-08 17:10
dbrower25628-May-08 17:10 
QuestionSTA handling events outside message pump? Pin
Irek Zielinski14-Mar-07 1:46
Irek Zielinski14-Mar-07 1:46 
AnswerRe: STA handling events outside message pump? Pin
soptest9-Oct-07 11:53
soptest9-Oct-07 11:53 
Questionc# sample of using IStream Pin
wakestar15-Dec-06 17:12
wakestar15-Dec-06 17:12 
QuestionWhy Invode can't work in ThreadFunc_ComThread Pin
gmay31-Aug-06 1:03
gmay31-Aug-06 1:03 
Generalthis is my question Pin
success_zhang23-May-06 3:31
success_zhang23-May-06 3:31 
GeneralRe: this is my question Pin
Lim Bio Liong23-May-06 16:40
Lim Bio Liong23-May-06 16:40 
GeneralRe: this is my question Pin
success_zhang24-May-06 1:14
success_zhang24-May-06 1:14 
QuestionGreat Work! Pin
herbertsg28-Apr-06 5:08
herbertsg28-Apr-06 5:08 
AnswerRe: Great Work! Pin
Lim Bio Liong28-Apr-06 8:22
Lim Bio Liong28-Apr-06 8:22 
GeneralRe: Great Work! Pin
herbertsg11-May-06 11:05
herbertsg11-May-06 11:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.