|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis 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 SynopsisListed below are the main sections of this article together with general outlines of each of their contents: COM Interface MarshalingThis 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 MarshalingThis 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 STAThis 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 The ThreadMsgWaitForSingleObject() FunctionA 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 MarshalingBackground On MarshalingIn 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 The process of marshaling an interface pointer inside an STA into a stream object is illustrated by the diagram below: 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 If we use OLE's implementation of stream objects, we can assume that it will be thread-safe and that its An unmarshaled interface pointer is also known as a proxy. It is an indirect pointer to the same interface of the original object. This proxy can be legally accessed by any thread in the importing apartment. When a method of the interface is called inside a thread of the foreign apartment, it is the proxy's responsibility to transfer control back to the object's own apartment and ensuring that the method invocation is executed inside this original apartment. This way, thread access rules are maintained in harmony across apartments. Hence, we can say that marshaling is the means by which thread access rules are maintained across apartments. The process of unmarshaling an interface pointer inside a stream object into a proxy usable inside an importing STA is illustrated by the diagram below: Marshaling TechniquesThere 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:
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:
In addition to the ways and means of performing it, there are two categories of explicit 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 MarshalingIn 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 The 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 /* 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
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 /* 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
Let us now thoroughly go through these two functions:
The low-level inter-apartment marshaling interaction can be illustrated in the diagram below: An Observation On The Proxy ObjectAn important observation that I hope the reader noticed is the fact that when [The return value of C/C++ functions which return integral values (e.g., The fact that when the reference count of the object behind 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 LowLevelInProcMarshalInterface() And LowLevelInProcUnmarshalInterface()The bulk of the work behind 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; }
Let us step through this function carefully:
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() 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; } }
Let us analyse this function carefully:
The purpose of calling Note that it is not enough to simply call Note that once Demonstrating The Higher-Level CoMarshalInterThreadInterfaceInStream() And CoGetInterfaceAndReleaseStream() APIsThis demonstration uses a higher-level set of API functions provided by COM to achieve marshaling and unmarshaling. This set of APIs are Please refer to the code listings of the /* 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
You will observe that the above synopsis is very similar to that of In fact, the internals of 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:
ExperimentationsWe mentioned earlier that Experiment 1Go to function 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 Experiment 2Before proceeding this second experiment, undo the changes to function Now, in function 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 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 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 /*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
Let us now go through these two functions in detail:
The inter-apartment marshaling interaction using the GIT can be illustrated in the diagram below: The GIT is most useful when many threads need to access one single interface pointer. Demonstrating An Invalid And Dangerous Cross-Thread Transfer Of Interface PointersPart 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 1Please refer to the code listings of the /* 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
Let us now go through these two functions in detail:
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 2The 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 " Private Declare Function GetCurrentThreadId Lib "kernel32" () As LongPublic Function TestMethod1() As Long
MsgBox "Thread ID : 0x" & Hex$(GetCurrentThreadId())
TestMethod1 = 0
End Function
Just like the 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 I will leave it to the reader to walk through the various parts of VCTest02 (i.e., the calling of functions | ||||||||||||||||||||