Introduction
COM Marshaling is a subject most developers can and do take for granted. Indeed the Microsoft COM engineers have done such a marvelous job of concealing the internals of marshaling that we are left totally unaware of the precision that take place with each cross-apartment method call.
This article is part one of a multi-part series of articles in which I plan to expound on the various methods of achieving COM Custom Marshaling. In this first installment, I will touch on the concept of a proxy and that of a marshal data packet. To this end, I have prepared basic sample implementation codes which we will walk through thoroughly. It is my hope that as we study this subject in-depth, we will come to realize and appreciate how marshaling works.
This series of articles is advanced in nature and I do expect the reader to possess prior knowledge on the following topics :
- COM development in general.
- Apartments (MTA and STA in particular).
- Interface marshaling in general (including the concepts of proxies and stubs).
Armed with the above-mentioned knowledge, we will now proceed to explore the world of custom marshaling. Let us start by going through some background on COM marshaling in general.
A Primer On COM Marshaling
Custom Marshaling.
The term custom marshaling is a misnomer. It is in fact a generic architecture that stipulates the rules and requirements of marshaling in general. What we know of as standard marshaling is actually a specific instance of custom marshaling . Any other forms of marshaling will be treated as various manifestations of custom marshaling. Each of these manifestations is transparent to the COM sub-system. For convenience of discussion, we will refer to standard marshaling as just that even though we know that it is Microsoft's specific implementation of custom marshaling. We will also regard all private, non-Microsoft marshaling as custom marshaling.
Marshaling is indelibly associated with apartments. We know 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). We also know that all COM objects live inside exactly one apartment.
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 then imported into the target (client) apartment. The resultant interface pointer which is used by the client apartment, however, will not be the original object itself. It is instead something known as a proxy. When COM performs the exporting and importing of interface pointers, it does so using a conjugate pair of procedures known as marshaling and unmarshaling. For the sake of convenience, we will refer to all types of client apartments (as mentioned above) as simply client apartments.
When a method of the imported interface is invoked, the proxy must somehow pass control to the original object (in its original apartment), get it to execute the method and then return control back to the proxy. In this way, it is ensured that a method is always executed in the correct apartment. When control is passed to the original object, the proxy's thread must halt until control is returned to the proxy.
This passing of control from one apartment to another is also known as method remoting. Method remoting is how all cross-thread, cross-process and cross-machine communications occur in COM.
Custom marshaling is essentially the generic mechanism that lets a COM object control the way it communicates with its proxy in a client apartment. The client apartment can either be in the same process as the COM object or in another process or even in a process in another machine. When we implement custom marshaling, what we eventually wind up doing is to implement a custom proxy.
We mentioned earlier that when a method of an imported interface is invoked, the proxy must somehow pass control to the original object. Under custom marshaling, the protocol for this is totally within the domain of the object and its proxy. COM plays no part in the communication process. However, when a proxy is created, COM gives the object a one time chance to pass something to the proxy. This is done to help the object establish its protocol with its proxy. This something is known as a marshal data packet. We shall explore these in greater detail in the coming sections below.
A term which will be covered in a future article but is worth mentioning at this time is that of a stub. Stubs are not always required although they can serve to simplify object-to-proxy communications. As will be made clear later on, stubs will not be relevant in the sample codes that we present in this article.
The IMarshal interface.
To support custom marshaling, a COM object must implement the IMarshal interface. If it does not, COM deems that standard marshaling is to be used whenever this object's interfaces are to be marshaled. Hence the first point about creating custom marshaling is to ensure that your COM object implements the IMarshal interface.
The second point about creating custom marshaling is that a proxy object must be defined. This proxy is also a COM object and it must also implement the IMarshal interface albeit not all of the IMarshal methods need to be non-trivial.
Note also that once an object has declared that it will implement custom marshaling, it must custom marshal all of its interfaces. The IMarshal interface consists of 6 methods. We will study these more in-depth when we explore our sample codes. For now, a short examination of how COM interacts with the IMarshal methods would be appropriate as much of what will be expounded on very soon afterwards will depend on a good understanding of this interface.
When called upon to perform marshaling, COM first queries the object for its IMarshal
interface. Once COM discovers that it supports IMarshal
, it will call its IMarshal::GetUnmarshalClass()
method. This method returns to COM the CLSID of the object that will be used for the unmarshaling process. In other words, COM is asking for the CLSID of the proxy that will be created when unmarshaling takes place. Having a CLSID implies that this proxy is a COM object.
Later, when unmarshaling is to occur, COM will use this CLSID to create the proxy object within the importing client apartment. Sensibilities dictate that such a COM object should be housed within a DLL and that it should support both the Single-Threaded and Multi-Threaded Apartments. These requirements will ensure that there will be no need for a proxy to the proxy that has just been created !
Still within the context of marshaling, COM will next call the IMarshal::GetMarshalSizeMax()
method to determine the size of its marshal data packet. We will start to go deep into marshal data packets in the next section. The IMarshal::MarshalInterface()
method of the object will then be invoked. From this method, COM expects to obtain the marshal data packet from the object. This marshal data packet can then either be passed to client code (for later unmarshaling) or be kept by COM itself inside a table in memory (to be retrieved multiple-times later by client code for unmarshaling).
Now, when unmarshaling takes place, a proxy COM object must first be created. Then the marshal data packet must be passed to it via its IMarshal::UnmarshalInterface()
method. This is how a proxy becomes initialized. Being initialized could mean that a proxy now has what it takes to communicate with its original object, or that a proxy constructs itself as a clone of the original object. Whichever is the case, once a proxy has been initialized, COM will no longer play any part in object-to-proxy communications.
The Marshal Data Packet
From a high-level point of view, marshaling and unmarshaling 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 client apartment which will reverse-transform the bytes back into an interface pointer usable by the client apartment.
The transformation of anything into a series of bytes is known as serialization. The series of bytes obtained from serialization is more commonly referred to as a stream. The serialization of an interface pointer is better known as marshaling the interface pointer. The stream obtained from marshaling is also known as a marshal data packet. The marshal data packet stream object is always referenced by a pointer to an IStream
interface.
The format of a marshal data packet used for custom marshaling is as follows :
The above diagram is a slightly modified version of the same taken from Don Box's book Essential COM (page 245).
The first part of the marshal data packet is a 4-byte signature which is hardcoded to the characters "MEOW" which is an acronym for Microsoft Extended Object Wire. This is followed by a single byte flags field. I personally do not know the purpose of this field. Next comes a 16-byte GUID field which is filled with the IID of the interface which is being marshaled. After that comes another 16-byte GUID field which is filled with the CLSID of the custom proxy which will be created in the client apartment.
We then have a 4 byte data the purpose of which is also unknown to me at this time. I resorted to calling this 4-byte field "Reserved" rather than speculate its actual intended purpose. The next field "Byte Count" is important as it indicates the length (in bytes) of the custom data which will follow.
This last section of the marshal data packet (i.e. "Custom Marshal Data") is particularly important. It is created by the original COM object and will be passed to a proxy during the proxy's initialization. To COM, this custom marshal data is essentially an opaque array of bytes that can contain anything as long as a proxy knows how to use it to establish communication with the original COM object or to re-create the object within the context of the client apartment. This array of bytes is the something that gets passed between an object and its proxy. This is the essence of custom marshaling.
We will revisit this structure again later when we go through our sample codes.
Note that unmarshaling may not always occur after an object has been marshaled. There is no rule stating this necessity. However because a marshal data packet potentially represents an object, its presence may warrant the need to add a reference count to the original object. This has to do with something known as strong and weak connections. We shall discuss this in a later article.
Basic Example
In this section, we present a simple example of marshaling known as "Marshal-by-Value". It is a perfect illustratory example for custom marshaling. The premise behind "Marshal-by-Value" is to create a proxy that is a clone of the original object. This concept applies to immutable objects (i.e. objects whose properties which will not change once they have been initialized).
Because immutable objects will never change the values of their properties, it makes no difference whether a proxy is a reference to the original object or is an exact copy of the object itself. This being the case, when a proxy to the object is required by a client apartment, we may as well clone the object and deliver it to the apartment.
And because a "Marshal-by-Value" proxy need not communicate with its original object, this example is simple enough for me to illustrate in-depth the proxy creation process. Proxy creation is a very important first step in understanding custom marshaling. Furthermore, since method calls will be made directly from the client code to the proxy, there will be no need for any marshaling of method arguments. This will be covered in a future article of this series.
The complete source codes for the basic example implementation are included in the zip file. Once unzipped, it will be stored in the following folder :
<main folder>\BasicSample01
where <main folder> is wherever you have copied the zip file to.
Inside BasicSample01, there are 3 additional folders : Interfaces, Implementations and Clients. Let us now examine the projects contained within these folders.
The BasicSample01Interfaces Solution
Inside the Interfaces folder, we have a Visual Studio .NET project folder named BasicSample01Interfaces in which the Visual Studio .NET solution project BasicSample01Interfaces.sln is found.
This BasicSample01Interfaces.sln project will not contain any useful implementation code. Its purpose is simply to allow us to automate (via ATL Wizards) the creation and maintenance of two COM interfaces named IImmutable
and IImmutableObjectFactory
.
Listed below is a fragment of code taken from BasicSample01Interfaces.idl showing the IImmutable and the IImmutableObjectFactory interfaces :
[
object,
uuid(BF0DC81A-46FB-4300-88E5-2B8EEB2CEEA1),
dual,
nonextensible,
helpstring("IImmutable Interface"),
pointer_default(unique)
]
interface IImmutable : IDispatch
{
[propget, id(1), helpstring("property LongValue")]
HRESULT LongValue([out, retval] LONG* pVal);
};
[
object,
uuid(CCAB57EA-A497-44C0-B0A4-E781B7F47AA9),
dual,
nonextensible,
helpstring("IImmutableObjectFactory Interface"),
pointer_default(unique)
]
interface IImmutableObjectFactory : IDispatch
{
[id(1), helpstring("method CreateObject")]
HRESULT CreateObject
(
[in] LONG InitializationValue,
[out,retval] IImmutable** ppIImmutableObjectReceiver
);
};
Note that the IImmutable
interface definition stipulates the basic attributes of this interface in terms of its GUID, methods, argument types, etc.
Customizable attributes like the apartment type, packaging format (in-proc DLL or EXE server) and whether standard or custom marshaling will be used, are the decision of the eventual implementation code.
An object which implements the IImmutable interface must possess a readonly long property. This readonly property must also never change once set internally.
The IImmutableObjectFactory
interface describes an object which serves as a factory of IImmutable
objects. An IImmutableObjectFactory
object must implement the CreateObject()
method which serves as an entry point for the creation and initialization of an IImmutable
object. This method takes a long parameter which is used as the value to be set for the long property of the IImmutable
object to be created and returned.
The BasicSample01Interfaces.sln solution will specifically help to maintain the BasicSample01Interfaces.idl file, which will be referenced by the BasicSample01InterfacesImpl.sln project (will be described in the next sub-section), and the BasicSample01Interfaces.h header file, which will be referenced by the various client codes included in the source codes zip file.
The BasicSample01InterfacesImpl Solution
Next, we examine the BasicSample01InterfacesImpl.sln project the folder of which can be found in the Implementations folder.
The BasicSample01InterfacesImpl.sln project contains concrete classes which implement the IImmutable
and IImmutableObjectFactory
interfaces. These are the CImmutableObjectFactoryImpl
and the CImmutableImpl
C++ classes respectively.
Let us now examine these classes in greater detail.
The CImmutableObjectFactoryImpl class
The CImmutableObjectFactoryImpl
class is implemented using ATL. It is an STA class but this fact is not important as we will not be performing any custom marshaling for this interface. If CImmutableObjectFactoryImpl
should be instantiated inside an STA thread, no marshaling will be immediately required in order that it be usable by the current apartment.However, if it is instantiated inside an MTA thread, then marshaling will be immediately required but we will be relying on standard marshaling for this.
Besides these immediate needs, any other cross apartment marshaling for CImmutableObjectFactoryImpl
's IImmutableObjectFactory
interface will also use standard marshaling.
Listed below is the implementation code for the only method of the IImmutableObjectFactory
interfce :
STDMETHOD(CreateObject)
(
long InitializationValue,
IImmutable ** ppIImmutableObjectReceiver
)
{
CComObject<CImmutableImpl>* pCImmutableImpl = NULL;
CComObject<CImmutableImpl>::CreateInstance(&pCImmutableImpl);
if (pCImmutableImpl)
{
CImmutableImpl* pBase
= dynamic_cast<CImmutableImpl*>(pCImmutableImpl);
if (pBase)
{
pBase -> SetLongValue(InitializationValue);
}
pCImmutableImpl -> QueryInterface
(
IID_IImmutable,
(void**)ppIImmutableObjectReceiver
);
}
return S_OK;
}
The CreateObject()
method simply creates an instance of CImmutableImpl
and then sets its long property value by calling its non-COM public function SetLongValue()
. The long value used here is taken from CreateObject()
's first parameter.
CreateObject()
then returns a pointer to the new CImmutableImpl
instance's IImmutable
interface via QueryInterface()
.
The CImmutableImpl class
The CImmutableImpl
class is also implemented using ATL. Like CImmutableObjectFactoryImpl
, it is designated as an STA object. This means that if it is instantiated inside an STA thread, marshaling will not be immediately required. However, if it is instantiated inside an MTA thread, marshaling will be immediately required. This would complicate matters a little. We will be demonstrating something similar to this in a more advanced sample in a future article. The current basic sample client code will not be touching on this. The cross-apartment marshaling we will be demonstrating in our basic sample will be across two STA apartments.
CImmutableImpl
supports the IImmutable interface by defining a long member data m_lLongValue
. This long value is set via the non-COM public function named SetLongValue()
. This method is only callable from code within the BasicSample01InterfacesImpl.dll server itself and only CImmutableObjectFactoryImpl
uses it to initialize a CImmutableImpl
object's long value.
The heart and soul of CImmutableImpl
, however, is in its implementation of the IMarshal
interface. CImmutableImpl
shows its support for the IMarshal
interface in its class definition :
class ATL_NO_VTABLE CImmutableImpl :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CImmutableImpl, &CLSID_ImmutableImpl>,
...
public IMarshal
{
...
...
...
}
The IMarshal
interface requires implementors to supply six methods. We shall examine CImmutableImpl
's implementations of these functions below.
GetUnmarshalClass()
HRESULT GetUnmarshalClass(
REFIID riid,
void * pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlflags,
CLSID * pCid
);
The GetUnmarshalClass()
method is fully documented in MSDN. It is the first IMarshal method that COM will invoke after determining that an object will be performing its own custom marshaling (through the discovery of the object's IMarshal interface). COM calls this method to determine the CLSID of the marshaling proxy object. It is the proxy that will perform the unmarshaling of the original object in the importing client apartment.
In our basic sample, because the proxy is an exact copy of the object itself, i.e., the CImmutableImpl
object is its own proxy. Hence the following code can be found in CImmutableImpl::GetUnmarshalClass()
:
STDMETHOD(GetUnmarshalClass)
(
REFIID riid,
void * pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlFlags,
CLSID* pCid
)
{
*pCid = GetObjectCLSID();
return S_OK;
}
We simply return the CLSID of the CImmutableImpl
class itself. This tells COM that once CImmutableImpl
has performed the necessary marshaling process and has acquired a marshal data packet stream (these are accomplished via the IMarshal::MarshalInterface()
method), COM should then create, in the target client apartment, another instance of the CImmutableImpl
class which will serve as the proxy to the original object.
The following diagram illustrates what happens when GetUnmarshalClass()
is called :
GetMarshalSizeMax()
HRESULT GetMarshalSizeMax(
REFIID riid,
void * pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlflags,
ULONG * pSize
);
GetMarshalSizeMax()
is the second IMarshal
method that COM will invoke. The purpose of this method is to determine the size of the marshal data packet to be created for marshaling the IImmutable
interface. This method works in pair with the MarshalInterface()
method as we shall see later. Once COM has determined this size value, it will proceed to prepare the stream object (a container for bytes) which will be used to store the marshal data packet.
CImmutableImpl
's implementation of GetMarshalSizeMax()
is listed below :
STDMETHOD(GetMarshalSizeMax)
(
REFIID riid,
void * pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlFlags,
ULONG* pSize
)
{
HRESULT hr = S_OK;
*pSize = sizeof(CImmutableMarshaledObjectReferenceStruct);
return hr;
}
Here we see that the size of the CImmutableMarshaledObjectReferenceStruct
structue will be returned. This is because we will be storing a CImmutableMarshaledObjectReferenceStruct
structure into the marshal data packet.
The CImmutableMarshaledObjectReferenceStruct
structure is listed below :
typedef struct
CImmutableMarshaledObjectReferenceStructTag
{
long lLongValue;
}
CImmutableMarshaledObjectReferenceStruct;
As mentioned earlier, to COM, the customized data contained inside the marshal data packet is essentially an opaque array of bytes that can contain anything as long as a proxy knows how to use it to establish communication with the original COM object or to re-create the object within the context of the client apartment. For our CImmutableImpl
class, this customized array of bytes will contain the CImmutableMarshaledObjectReferenceStruct
structue. As we shall see later on, this structure contains all the necessary information for our proxy to re-create the original object in the client apartment.
The following diagram illustrates what happens when GetMarshalSizeMax()
is called :
MarshalInterface()
HRESULT MarshalInterface(
IStream * pStm,
REFIID riid,
void * pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlflags
);
The MarshalInterface()
method is the next method to be called. This is a very important method as it is here that the marshal data packet will be created and end up being serialized and marshaled across to a target apartment. Whatever we place into the marshling data packet should contain information that will help the proxy to uniquely identify and establish connection with the original CImmutableImpl
object (in order to invoke its properties and methods) or to re-create it in the context of the client apartment.
CImmutableImpl
's MarshalInterface()
method is listed below :
STDMETHOD(MarshalInterface)
(
IStream* pStm,
REFIID riid,
void *pv,
DWORD dwDestContext,
void * pvDestContext,
DWORD mshlFlags
)
{
if (dwDestContext != MSHCTX_INPROC)
{
return E_FAIL;
}
CImmutableMarshaledObjectReferenceStruct
ImmutableMarshaledObjectReferenceStruct;
ImmutableMarshaledObjectReferenceStruct.lValue = m_lValue;
*pStm << ImmutableMarshaledObjectReferenceStruct;
return S_OK;
}
We stipulate that the marshaling context must be MSHCTX_INPROC
(ie. the unmarshaling will be done in another apartment in the same process). The other marshaling contexts are not supported in our basic example.
We essentially create a CImmutableMarshaledObjectReferenceStruct
structure and fill its only member data lValue
with the current value in m_lValue
. Once this is done, we will write the contents of our structure into the stream. We use a global stream support function (listed in marshalhelpers.h) to perform this.
The CImmutableMarshaledObjectReferenceStruct
structure is used to store the current value of the m_lValue
member data (which is returned when the IImmutable
interface's LongValue
property is requested by a client). This structure will be contained as raw bytes in the marshal data packet and will be passed to the IMarshal::UnmarshalInterface()
method of the proxy which will reverse-transform these raw bytes into a CImmutableMarshaledObjectReferenceStruct
structure. More on this later.
The following diagram illustrates what happens when MarshalInterface()
is called :
UnmarshalInterface()
HRESULT UnmarshalInterface(
IStream * pStm,
REFIID riid,
void ** ppv
);
The UnmarshalInterface()
method is to be implemented by a proxy. It is meant to be the congugal opposite of the MarshalInterface()
method which is meant to be implemented by the original object. However, CImmutableImpl
provides an implementation because it is its own proxy class. What happens before UnmarshalInterface()
is invoked is that a new instance of CImmutableImpl
(playing the role of a proxy) will be created inside the client apartment. Thereafter, this new instance's UnmarshalInterface()
method will be called with an IStream
pointer of the stream object which contains the marshal data packet created previously during the MarshalInterface()
call.
What is expected from the UnmarshalInterface()
method call is to return an interface pointer (whose IID is specified by the riid parameter) from the unmarshaled object (i.e. the proxy).
Let us examine CImmutableImpl
's UnmarshalInterface()
method :
STDMETHOD(UnmarshalInterface)
(
IStream* pStm,
REFIID riid,
void ** ppv
)
{
CImmutableMarshaledObjectReferenceStruct
ImmutableMarshaledObjectReferenceStruct;
*pStm >>
(CImmutableMarshaledObjectReferenceStruct&)
ImmutableMarshaledObjectReferenceStruct;
m_lValue = ImmutableMarshaledObjectReferenceStruct.lValue;
return QueryInterface(riid, ppv);
}
We create a new instance of the CImmutableMarshaledObjectReferenceStruct
struct which is used to download all the contents of the input stream. Thereafter, we reconstruct CImmutableImpl
by retrieving the lValue
member of the structure and filling m_lValue
with this value. This is exactly how the proxy becomes a "Marshal-By-Value" proxy. The original object is effectively re-created. And in our simple example, this completes the reconstruction process.
We then perform a QueryInterface()
to retrieve the required interface pointer. The interface required is none other than that of IImmutable (i.e., IID_IImmtable
).
The following diagram illustrates what happens when UnmarshalInterface()
is called :
ReleaseMarshalData()
HRESULT ReleaseMarshalData(
IStream * pStm
);
This method is COM's way of indicating to an object (which has been marshaled) that a marshal object packet is being destroyed. The ocurrence of this is a very important event. However it is not significant in our basic example, as will be the case for most "Marshal-by-value" proxies. It is related to two concepts known as table marshaling and strong connections. In general only strong table-marshaled objects need to provide a non-trivial implementation of ReleaseMarshalData()
.This topic will be discussed again in a more advanced article in the future.
CImmutableImpl
's version of ReleaseMarshalData()
is listed below :
STDMETHOD (ReleaseMarshalData)(IStream *pStm)
{
return S_OK;
}
It is a trivial implementation indeed.
DisconnectObject()
HRESULT DisconnectObject(
DWORD dwReserved
);
This method applies only to objects which are housed within EXE COM servers. The typical situation in which this method is called on an object is when the EXE COM server is forcibly terminated while it still has at least one running object. In this situation, prior to shutting down, the EXE server must invoke the CoDisconnectObject()
API on each of these running objects. For each object that implements IMarshal
, CoDisconnectObject()
will call the IMarshal::DisconnectObject()
method so that each object that manages its own marshaling can take steps to notify its proxy that it is about to shut down.
After this is done, clients which hold proxies may continue to call the proxy's methods but the proxy must then return the HRESULT CO_E_OBJECTNOTCONNECTED
.
This method is equally irrelevant to "Marshal-by-Value" proxies because a proxy is a clone of the original object and will not need to communicate with it. The above-described scenario do not apply for our basic example. Hence CImmutableImpl
's version of DisconnectObjectI()
is also trivial :
STDMETHOD (DisconnectObject)(DWORD dwReserved)
{
return S_OK;
}
The Client Solutions
I have prepared 3 separate client projects each of which illustrate a slightly different way to marshal and unmarshal our immutable object across apartments. These solutions are contained within the Clients folder. Let us examine these projects individually.
The VCConsoleClient01 Solution
The VCConsoleClient01.sln project provides a good basic example of demonstrating cross-apartment marshaling and unmarshaling with the lowest-level techniques available : i.e. via the CoMarshalInterface()
and CoUnmarshalInterface()
APIs and the creation of a stream object via CreateStreamOnHGlobal()
.
One of the highlights of the VCConsoleClient01 solution is our examination of the contents of the marshal data packet that will be created after we call the CoMarshalInterface()
API.
VCConsoleClient01.sln is a console-based application. The following is a synopsis of this application :
- A thread is started in which an instance of the
IImmutableObjectFactory
object is created and from which an instance of the IImmutable
object is gotten.
- The
IImmutable
object is then marshaled and a marshal data packet will be created for it.
- The marshal data packet is then transported across to another apartment where it is examined and the
IImmutable
interface contained inside is imported.
- After importing the
IImmutable
interface, we will use the proxy to call a method of the IImm<CODE>
utable interface.
The source codes of the VCConsoleClient01 solution is simple enough for us to break it down into its constituent components and have them analyzed individually. These are summarized below :
The _tmain() function
The _tmain()
function is the entry point of the application. It is listed below :
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
g_hInterfaceMarshaled = CreateEvent(NULL, TRUE, FALSE, NULL);
Demonstrate_Cross_Apartment_Call_Via_Stream();
if (g_hInterfaceMarshaled)
{
CloseHandle(g_hInterfaceMarshaled);
g_hInterfaceMarshaled = NULL;
}
::CoUninitialize();
return
0;
}
Nothing sophisticated about the _tmain
function. It essentially initializes the thread that it runs on to be an STA thread. It then creates an event handle g_hInterfaceMarshaled which will be signalled when the IImmutable object has been marshaled. _tmain
than calls the Demonstrate_Cross_Apartment_Call_Via_Stream
function. This function is where the real action starts.
The Demonstrate_Cross_Apartment_Call_Via_Stream() Function
The Demonstrate_Cross_Apartment_Call_Via_Stream()
function is where the good stuff begins. This function works hand-in-hand with another named ThreadFunc_CustomMarshaledObject()
to provide a complete demonstration of exporting and importing an interface pointer from one apartment to another.
Due to its length, we will not be printing a complete listing in this section. Instead we will provide a general synopsis of this function. Specific parts of this function will be examined closely in a later section when we discuss how it works together with ThreadFunc_CustomMarshaledObject()
.
Here below is a general synopsis :
- It creates a thread headed by the entry point function
ThreadFunc_CustomMarshaledObject()
.
ThreadFunc_CustomMarshaledObject()
will create an IImmutable object (through a IImmutableObjectFactory
object) and initialize its long value to some number. After creating the IImmutable
object, it will create a marshal data packet for it and pass it (via an IStream interface pointer) to Demonstrate_Cross_Apartment_Call_Via_Stream()
.
- After receiving the IStream interface pointer to the marshal data packet,
Demonstrate_Cross_Apartment_Call_Via_Stream()
will cast it to a MarshalDataPacket structure for examination purposes.
- Next, the marshal data packet is unmarshaled and we will then obtain a proxy to the original IImmutable object. However, because the proxy is actually a "Marshal-By-Value" proxy, what we will obtain is actually a clone of the original IImmutable object.
- We will use the proxy to obtain the long value contained inside. We will note that the long value is indeed the one that was used to initialize the original IImmutable object.
The ThreadFunc_CustomMarshaledObject() Function
As mentioned previously, ThreadFunc_CustomMarshaledObject()
works hand-in-hand with Demonstrate_Cross_Apartment_Call_Via_Stream()
.
Here below is a general synopsis of this function :
ThreadFunc_CustomMarshaledObject()
is the entry point function of a thread.
- It is initialized to be an STA thread.
- An instance of the
IImmutableObjectFactory
object is created and from this, an instance of a IImmutable object is obtained.
- The IImmutable object is then marshaled. The resultant marshal data packet is then transported to the
Demonstrate_Cross_Apartment_Call_Via_Stream()
function.
ThreadFunc_CustomMarshaledObject()
then enters a message loop which will not break until a WM_QUIT
message is posted to this thread.
- The fact that this thread remains alive (via the message pump) means that the IImutable object will also remain alive for perusal by other apartments.
How do the two functions work together ?
In this section, we examine how the above two functions work together to demonstrate marshaling/unmarshaling. We will also see the IMarshal
methods of the IImmutable
object in action while marshaling and unmarshaling take place. We will describe what goes on chronologically starting from the Demonstrate_Cross_Apartment_Call_Via_Stream()
function.
Demonstrate_Cross_Apartment_Call_Via_Stream()
starts by creating a new thread headed by the entry point function ThreadFunc_CustomMarshaledObject()
.
- It also defines an uninitialized pointer to an IStream interface named
lpStream
:
LPSTREAM lpStream = NULL;
- We will pass the address of lpStream to the
CreateThread()
API as the parameter to the ThreadFunc_CustomMarshaledObject()
entry point function :
hThread = CreateThread
(
(LPSECURITY_ATTRIBUTES)NULL,
(SIZE_T)0,
(LPTHREAD_START_ROUTINE)ThreadFunc_CustomMarshaledObject,
(LPVOID)(&lpStream),
(DWORD)0,
(LPDWORD)&dwThreadId
);
This is done so that it will be initialized by ThreadFunc_CustomMarshaledObject()
to point to the marshal data packet for the IImmutable object which will be created in that thread.
- After creating the thread,
Demonstrate_Cross_Apartment_Call_Via_Stream()
will wait for the g_hInterfaceMarshaled
event handle to be set :
ThreadMsgWaitForSingleObject(g_hInterfaceMarshaled, INFINITE);
ResetEvent(g_hInterfaceMarshaled);
- This event handle was first created inside
_tmain()
. It will be set inside ThreadFunc_CustomMarshaledObject()
when the IImmutable
object has been marshaled :
if (g_hInterfaceMarshaled)
{
SetEvent(g_hInterfaceMarshaled);
}
When this event handle is set, it is a signal to the Demonstrate_Cross_Apartment_Call_Via_Stream()
function that its lpStream
now holds a marshal data packet and is ready for use.
- On the
ThreadFunc_CustomMarshaledObject()
side, the pointer to lpStream
is represented by the local variable named ppStreamReceiver
. It will be initialized to the value of the LPVOID
parameter passed from Demonstrate_Cross_Apartment_Call_Via_Stream()
which is actually the address of the IStream
pointer lpStream
:
DWORD WINAPI ThreadFunc_CustomMarshaledObject(LPVOID lpvParameter)
{
MSG msg;
long lLong = 0;
LPSTREAM* ppStreamReceiver = (LPSTREAM*)lpvParameter;
...
...
...
ThreadFunc_CustomMarshaledObject()
will then proceed to create an IImmutableObjectFactory object and thereafter, to obtain an IImmutable object from it :
_CoCreateInstance
(
"BasicSample01InterfacesImpl.ImmutableObjectFactoryImpl",
spIImmutableObjectFactory
);
spIImmutableObjectFactory -> CreateObject
(
101,
(IImmutable**)&spIImmutable
);
Note that IImmutable
object's long
property will be initialized to a value of 101.
ThreadFunc_CustomMarshaledObject()
will then create a stream object using the CreateStreamOnHGlobal()
API :
::CreateStreamOnHGlobal
(
0,
TRUE,
ppStreamReceiver
);
The CreateStreamOnHGlobal()
API is used to create a Stream Object which will reside in global memory. We set the first parameter to zero to indicate 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.
The newly allocated stream object will be returned as an IStream
pointer. CreateStreamOnHGlobal()
expects a pointer to a LPSTREAM
in its third parameter. For this, we pass ppStreamReceiver
. This way, lpStream
(from Demonstrate_Cross_Apartment_Call_Via_Stream()
) is set to store the resultant pointer to the IStream
interface of the newly created stream object.
- The
CoMarshalInterface()
API is then used to create a marshal data packet for the IImmutable
object :
::CoMarshalInterface
(
*ppStreamReceiver,
__uuidof(IImmutablePtr),
(IUnknown*)pIUnknown,
MSHCTX_INPROC,
NULL,
MSHLFLAGS_NORMAL
);
For the first parameter, CoMarshalInterface()
expects a pointer to a stream object to be used during marshaling. For this, we pass the contents of ppStreamReceiver
. This way, the lpStream
(from Demonstrate_Cross_Apartment_Call_Via_Stream()
) is used to store the marshal data packet of the IImmutable
object. The use of the MSHCTX_INPROC
as the destination context (parameter 4) indicates that the unmarshaling of the data in the stream will be done in another apartment of the same process.
The use of the MSHLFLAGS_NORMAL
flag (parameter 6) indicates that the marshal data packet can be unmarshaled just once, or not at all.
During the course of running CoMarshalInterface()
, COM will attempt to get the CLSID of the proxy for the IImmutable object and then to get the marshal data packet from it.
The following sequence of function calls on the side of the IImmutable
object will take place :
CImmutableImpl::GetUnmarshalClass()
CImmutableImpl::GetMarshalSizeMax()
CImmutableImpl::MarshalInterface()
The reader may want to put a breakpoint in these functions for observation purposes.
- When
CImmutableImpl::GetUnmarshalClass()
is called, we will indicate to CoMarshalInterface
the CLSID of the object that will take charge of unmarshaling the interface pointers of the IImmutable
object. This will be the CLSID of the CImmutableImpl
object itself. Note that the parameters to GetUnmarshalClass
will reflect the parameter values of CoMarshalInterface
.
- When
CImmutableImpl::GetMarshalSizeMax()
is called, we will pass the size of the CImmutableMarshaledObjectReferenceStruct
structure.
- When
CImmutableImpl::MarshalInterface()
is called, notice that we create a CImmutableMarshaledObjectReferenceStruct
structure and then serialize it into the stream object whose IStream interface is passed as first parameter.
- After
CoMarshalInterface()
completes, we must reset the stream to the beginning. Otherwise a later call to the CoUnmarshalInterface()
API using this same stream object will fail with error STG_E_READFAULT
:
(*ppStreamReceiver) -> Seek(li, STREAM_SEEK_SET, NULL);
Demonstrate_Cross_Apartment_Call_Via_Stream()
will then pass lpStream
to the ExamineStream()
function for a closer look at what the marshal data packet contains. More on ExamineStream()
later.
Demonstrate_Cross_Apartment_Call_Via_Stream()
will then pass lpStream
to the CoUnmarshalInterface()
API to construct a proxy to the IImmutable
interface from the stream :
::CoUnmarshalInterface
(
lpStream,
__uuidof(IImmutablePtr),
(void**)&spIImmutable
);
- Note that at this point in time, the Marshal Data Packet is contained inside the stream. All COM needs is a new proxy object to which it passes the Marshal Data Packet (in the
UnmarshalInterface
method call). When CoUnmarshalInterface
is called with our marshal data packet stream, COM will create a proxy to the IImmutable
object created inside the ThreadFunc_CustomMarshaledObject
thread.
- During the course of execution of the
CoUnmarshalInterface
API, COM will pass the marshal data packet stream to the proxy in order for it to be initialized. The following sequence of function calls will take place :
CImmutableImpl()
(constructor)
CImmutableImpl::FinalConstruct()
(part of proxy construction)
CImmutableImpl::UnmarshalInterface()
(the marshal data packet will be passed here).
The reader is encouraged to place breakpoints inside these functions to see the sequence of CImmutableImpl
function calls.
- Notice that in
CImmutableImpl::UnmarshalInterface(),
a reverse of the activities of CImmutableImpl::MarshalInterface()
is performed. A proxy which is effectively a clone of the original IImmutable
object is constructed.
- After the proxy to the
IImmutable
object has been created and initialized, Demonstrate_Cross_Apartment_Call_Via_Stream()
will proceed to call its get_LongValue()
method :
if (spIImmutable)
{
spIImmutable -> get_LongValue(&lLongValue);
}
The value 101 will be returned, proving that the proxy is indeed a clone of the original object.
- Thereafter
Demonstrate_Cross_Apartment_Call_Via_Stream
will terminate the ThreadFunc_CustomMarshaledObject
thread by posting a WM_QUIT
message to it :
PostThreadMessage(dwThreadId, WM_QUIT, 0, 0);
- When
ThreadFunc_CustomMarshaledObject
receives the WM_QUIT
message, it will exit its message loop and head towards the end of the function. CoUninitialize
is called and the IImmutableObjectFactory
and IImmutable
smart pointer objects will be destroyed, thereby Release()
'ing them.
The ExamineStream() Function
The ExamineStream
function uses standard IStream
manipulation APIs to cast the data contained inside a stream into a MarshalDataPacket
structure. This structure is listed below :
struct MarshalDataPacket
{
char Signature[4];
char chFlags;
IID iidMarshaledInterface;
CLSID clsidCustomProxy;
unsigned long ul;
unsigned long ulSize;
unsigned char ucCustomMarshalDataBytes[1];
};
Notice that it is a complete representation of the marshal data packet illustrated in the "The Marshal Data Packet" section. The MarshalDataPacket
struct is defined to help us cast the contents of a marshal data packet to a structure which will make its analysis much easier.
The ExamineStream
function will allocate in memory a buffer which is the size of the stream object pointed to by the lpStream
parameter. After that, its contents are copied into a MarshalDataPacket
structure instance.
The diagram below shows a QuickWatch on the field values of this structure :
The format of the marshal data packet is revealed. We can see how the raw bytes of the customized part of the marshal data is used to contain the value of the lLongValue
field (value 101) of the CImmutableMarshaledObjectReferenceStruct
structure.
The Other Client Solutions
I have included two other client solutions which are contained inside the VCConsoleClient02 and VCConsoleClient03 folders. We do not have to go through their code as they are each sufficiently similar to VCConsoleClient01. My intention is to demonstrate other methods of marshaling and unmarshaling interfaces across apartments.
The following is a summary of their special features :
VCConsoleClient02
Instead of CoMarshalInterface()
and CoUnmarshalInterface()
, CoMarshalInterThreadInterfaceInStream()
and CoGetInterfaceAndReleaseStream()
are used. Please refer to MSDN for details on these two APIs.
These two APIs are actually warppers for the elaborate work that we did in VCConsoleClient01 of constructing a stream object and then using it to create a marshal data packet for marshaling and unmarshaling.
VCConsoleClient03
VCConsoleClient03 presents a radically different way of managing marshal data packets and proxies. It uses the Global Interface Table (GIT) to store the marshal data packet of an object. Whenever a proxy to the object is required, it is first created in the client apartment, then the marshal data packet from the GIT is retrieved and used to initialize the proxy.
In this example, the reader will see the IMarshal::ReleaseMarshalData()
method in action. This method is COM's way of indicating to an object (which has been marshaled) that a marshaled object packet is being destroyed. A marshaled object reference can be considered an additional reference to an object. Hence, a call to ReleaseMarshalData
should signal an object to perform reference count state management. This is especially true of objects which have been table-marshaled (i.e. their marshal data packets are stored in a table somewhere, e.g. the Global Interface Table) However it is not significant in our basic example, as will be the case for most "Marshal-by-value" proxies. This is because a "Marshal-by-value" proxy is independent of its original object.
Conclusion
In this first part of a multi-part dissertation on custom marshaling, I have attempted to provide an in-depth analysis of a proxy and a marshal data packet. In part two, we will go deeper by studying more advanced examples of custom marshaling. For the first time, we will touch on the role of stubs in custom marshaling.
Acknowledgements And References
Essential COM by Don Box. Published by Addison-Wesley