Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > March 1997
March 1997


Code for this article: activexcode0397.exe (6KB)
Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp.

Q I am in the process of moving a large C++-based system over to DCOM and I have run into a problem. I often need to pass C++ objects as method parameters. Unfortunately, there is no way for me to define my C++ classes in IDL, which I believe is required to make this happen. I think that accessing my objects via proxy/stub connections (which is what would happen if I simply COM-enabled my C++ classes) will cause way too much network traffic. Is there some way to just transmit the object state when it is used as a method parameter?
Lorrie Trussell
Butte, Montana

A The problem you have encountered is common when integrating code written in the PC (pre-COM) era. IDL, the lingua franca of COM, is extremely expressive. However, it usually does not allow you to violate the basic principles of the model. The principle that you are attempting to violate is that of separation of interface from implementation. To understand why what you want to do is not feasible, it helps to examine the most primitive underpinnings of COM.
In COM, developers create strict contracts that define what a particular functionality will look like. We call these contracts interfaces. One of the most important aspects of a COM interface is that it is polymorphic (it can be implemented by many different types of objects) and does not betray the details of any particular implementation. For example, the interface definition


 [ 
  uuid(2DEFACE-ABBA-1F99-C000-000000000046), 
  object
 ]
 interface IMessageSink : IUnknown 
 { 
    HRESULT OnMessageAvailable([in] DWORD dwID);
    HRESULT OnUrgentMessage([in] DWORD dwID, 
                            [in] DWORD dwPriority);
 }
simply describes the operations that an object must implement to be IMessageSink-compatible. The interface definition in no way mandates how any given object will implement OnMessageAvailable or OnUrgentMessage; it only states that all IMessageSink-compatible objects will have some reasonable implementation of these methods. The definition of "reasonable" is also part of the interface definition, but cannot be expressed in IDL, which is only capable of describing syntax. Instead, the semantics of each method must be defined offline in accompanying documentation.
IDL 3.0 does allow implementations to be defined in addition to interfaces, but only in the vaguest of terms. The IDL definition of a COM implementation is only used to advertise which interfaces an implementation supports, not how the object will actually achieve this. For example, the implementation definition

 [uuid(00000001-FEED-FEED-FEED-BEEFBEEFBEEF)]
 coclass Pager
 {
   interface IMessageSink;
   interface IBatteryOperatedDevice;
   interface IAnnoyingModernNuisance;
 }
is sufficiently vague that it allows the Pager implementor complete flexibility not only in terms of how to implement the object, but also in terms of what language to use for the implementation. In fact, over time, the implementation details of Pager can evolve without the client being aware that any change has taken place. Therein lies the problem.
You initially wanted to define your C++ classes in IDL. While COM is often accused of being "just C++ with weird naming conventions," C++ is very different from COM in terms of design philosophy. In C++, there is no notion of separation of interface from implementation. A C++ class definition contains elements of both interface and implementation. Because of this, C++ classes do not quite fit into the COM model of an interface.
Consider the (illegal) IDL shown in Figure 1. The interface designer made several astounding assumptions about all implementations of the ILookup interface. First, all implementations must be in C++, as no other language is capable of dealing with the abomination defined in this code. Second, because the class definition for CAddressBook uses several non-trivial language features (such as virtual bases and exceptions), no two C++ compilers are likely to produce binary-compatible versions of CAddressBook. This effectively means that all clients and implementations must standardize on a particular version of a C++ compiler and stick with it from now until the end of time. Finally, if it’s inefficient to implement CAddressBook using only the two data members, it would be impossible to add new members without breaking the interface. The data members of CAddressBook are part of the binary contract of ILookup due to the compilation model of C++. This IDL ignores the COM philosophy of separation of interface and implementation. Shame on you for ever wanting to do something like that!
Despite the problems related to using C++ classes as method parameters, it is still a common requirement to pass C++-based objects as method parameters. Given this, you need to examine options that do not violate any of the constraints of COM. The first constraint is that all COM method parameters must be compatible with IDL. IDL supports a family of built-in base types, which are a superset of the built-in types in C++, and are listed in Figure 2. In addition to these base types, IDL also supports C-style pointers, enumerations, arrays, structures, and unions. This is the good news. The bad news for many C++ developers is that a fair number of C++-isms are not supported, including classes, structs with member functions, scoping, templates, function and operator overloading, and exceptions.
With these limitations in mind, the most reasonable approach is to COM-enable your C++ classes and pass interface pointers that refer to your objects. If you apply this approach, the previous IDL fragment becomes much simpler (see Figure 3. Based on these IDL definitions, you could then COM-enable your implementation of CAddressBook so developers working in any language can pass references to your class as a parameter. This would require at the very least adding about 20 lines of boilerplate code to implement IUnknown. To instantiate it outside of your executable, you would also need to expose a class factory, which would require another 70 or so lines of code that are fairly type-independent and easily cannibalized from existing sources. Feel free to grab my personal COM skeleton from http://www.develop.com/dbox/donskel.htm.
Assuming that COM support is integrated into your C++ class, the class is now available as a parameter type via its exposed interfaces. Because it supports COM, your class is now available to programmers who are using languages other than C++. This is great. Assuming that your object just supports IUnknown and a handful of custom interfaces, your object will use standard marshaling whenever it is passed as a method parameter to a remote object. With standard marshaling, the COM remoting layer will automagically create a stub for your object when it is passed as a parameter (for example, as a parameter to ILookup::LookupAddress). The COM remoting layer will also build a proxy on the other end of the connection so that clients in remote address spaces can talk to you. Figure 4 shows the schematic for how this works.
Figure 4 Standard Marshaling
Figure 4 Standard Marshaling

The rosy picture portrayed in Figure 4 is great for a large class of objects. If your object has data members that are file handles, the method calls will execute in the process where the handle was opened, which is a very good thing. If your object has pointers to other objects in the same process, your method implementations will have direct access to them. This is also a good thing. Additionally, all method calls will execute using your process’s security token, which may be good or bad, depending on the semantics of your object, but with impersonation you can use the token of the caller if needed. In general, standard marshaling is a very good thing.
Despite the tremendous benefits noted above, standard marshaling breaks down rapidly for several classes of objects. One classic example is the immutable object, an object that is constant and does not change its state. To understand how standard marshaling is inefficient for such an object, consider a C++ class that has 20 data members set at initialization time that never change. Assuming that this class was created during the 1980’s or early 1990’s, when most programmers bought into the idea of "just enough" encapsulation, the class definition would look like this:

 class Computer 
 {
   string m_make;
   string m_model;
   int m_clockSpeed;
   int m_ramSize;
 •
 •
 •
 public:
   Computer(void) { // init data members }
   const string& GetMake() const {return m_make;}
   const string& GetModel() const {return m_model;}
   int GetClockSpeed() const {return m_clockSpeed;}
   int GetRamSize() const {return m_ramSize;}
 •
 •
 •
 };
If you were to apply the naïve COM-ification to this class you would wind up with a COM interface that looks something like this:

 [
   uuid(FACE0B0B-0000-0000-0000-C00000000046), 
   object
 ]  
 interface IComputer : IUnknown
 {
   HRESULT GetMake([out, string] OLECHAR **ppwsz);
   HRESULT GetModel([out, string] OLECHAR **ppwsz);
   HRESULT GetClockSpeed([out] long *pn);
   HRESULT GetRamSize([out] long *pn);
 •
 •
 •
 };
If this interface was used in a standard marshaling scenario, it would require n client-server roundtrips to retrieve the entire state of the object (where n is the number of Get routines). Since roundtrips are the number-one enemy of any COM programmer who cares about performance, it would be really nice if the object could just be teleported into the recipient’s address space and all of these Get operations could be inproc calls, which are virtually free in terms of performance. Enter custom marshaling.
In COM, the closest you can get to passing an "object" as a parameter is to pass an interface pointer to the object as a parameter. Also, interfaces are polymorphic so you can’t make any assumptions about the implementation of the object that is providing the interface. Custom marshaling is based on these two aspects of COM. When an interface pointer is marshaled as a parameter, the COM remoting layer (CoMarshalInterface, to be exact) asks the object if it wants COM to set up the proxy/stub communications shown in Figure 4. This question comes in the form of a QueryInterface request for a distinguished interface, IMarshal. If your object does not support this interface (most objects do not, hence the name standard marshaling), then COM assumes that proxy/stub communications will be good enough and it transparently hooks you into a new remote connection. However, if you said "yes" in the QueryInterface call, your object will be given the opportunity to establish its own proprietary communication with the recipient. This connection establishment is based on the IMarshal interface shown in Figure 5.
Objects that expose the IMarshal interface are said to support custom marshaling. If your object exposes IMarshal, no stub will be created in your process to receive the network packets that correspond to remote method call requests. Instead, you will be given the opportunity to send an arbitrary stream of bytes to the remote execution context in a one-time, connection-establishment message. Once this message is received in the remote context, COM will create an inproc object of a class that you specify to read the connection-establishment message at the recipient. This inproc object must then return an interface pointer that is semantically "good enough" for accessing the original object. The inproc object created by COM to read the message is called the unmarshaler. The inproc object returned to the recipient may just be a different interface pointer to the unmarshaler itself, or it may be a pointer to a different object. In either case, the recipient gets an interface pointer that is semantically equivalent to the original object.
Figure 6 Custom Marshaling
Figure 6 Custom Marshaling

To see how this process works, take a look at Figure 6. The remoting layer will first ask your object how many bytes you need for your connection-establishment message. It will then allocate a transmission buffer big enough to hold your message plus some header information. Once the transmission buffer is allocated, the remoting layer (CoMarshalInterface) will ask for the CLSID of the unmarshaler that will be instantiated to read your message. This CLSID will be sent along with your message. Finally, your object is given an opportunity to write the message via its MarshalInterface method. You are told how far away the recipient is (same machine? same process?) and which interface is being marshaled. In your implementation of MarshalInterface, you write out whatever information is needed to enable your unmarshaler to return something reasonable to the receiver. Once the MarshalInterface method returns, the transmission buffer is sent to the recipient, probably via a normal standard marshaling connection to some other object.
When the buffer is received in the remote context, the receiving code (probably a proxy or stub) will pass the buffer to COM’s remoting layer (CoUnmarshalInterface), which will decode the packet into an interface pointer. The buffer will contain a flag indicating that the packet contains a custom marshaled interface. This flag tells COM to read the CLSID from the packet (this is the CLSID that your object provided in its GetUnmarshalClass method) and create the unmarshaler that knows how to read the connection-establishment message. Once your unmarshaler has been instantiated via CoCreateInstance, your unmarshaler’s UnmarshalInterface method will be used to read the packet and return an interface pointer to the recipient.
Given this information, you now have the escape hatch needed for sending the state of the object to the remote recipient. In referring to what gets sent from the object to the unmarshaler, I was deliberately vague in calling it a connection-establishment message. This is just an arbitrary stream of bytes that is written by your MarshalInterface method and can contain anything that is needed to generate a semantically correct pointer in the recipient’s process. If your object never changes its state, then passing the serialized state of the object in this message would allow the unmarshaler to simply deserialize itself to create an exact clone in the recipient’s address space. Once unmarshaled, this clone would need no additional communications with the original object and all of its methods would execute in the address space of the client. Objects that implement IMarshal in this manner are said to "marshal-by-value," as the marshaled state of an interface pointer to the object is just the object’s actual state. Like pass-by-value in C or C++, no changes that are made in the recipient are propagated back to the original object. For immutable objects this is fine since the object does not support any operations that would change the state of the object anyway.
The parallels between marshal-by-value and persistence are striking. Both require the object to serialize its state into a byte store. Both require the object to provide a CLSID for the implementation that knows how to read the serialized state. Both require the "deserializer" to read the data from the byte store and provide a rehydrated object. Nothing emphasizes this more than examining the IPersistStream interface side-by-side with IMarshal, as is shown in Figure 7. Given the parallel structure of these two interfaces, it might make sense to implement IMarshal in terms of IPersistStream. IPersistStream is somewhat simpler, and is more likely to be supported by your favorite COM framework. Figure 8 and Figure 9 show a simple aggregatable helper object that exposes IMarshal and uses the outer object’s IPersistStream implementation to perform the serialization. Figure 10 shows an object that uses this helper to implement marshal-by-value. Note that for an object that already implements IPersistStream, you only need to modify QueryInterface slightly to create the helper object when asked for IMarshal. By adding about eight lines of code, your object now marshals by value.
Figure 8 CMarshalByValue
Figure 8 CMarshalByValue

You may be thinking that by using the helper object shown in Figure 8 you just pushed the problem up one layer to that of implementing persistence. This observation is correct. To use the helper object, you now need to implement serialization via IPersistStream. For a simple object (like the one shown in Figure 10), this is fairly simple: you can just call Read or Write to dump the data members of the class into the provided IStream interface. However, this ignores several important issues that occur in "real" objects. First, the Read/Write calls of an IStream interface only transfer the bytes opaquely. This means that if you marshal the object from a Windows NT®-based machine and unmarshal it on a Solaris or MacOS machine, the endianness (byte order) of your data will be incorrectly unmarshaled. Also, if your object has pointers as data members or is fairly complex, simply bitwise copying the data members to the stream is insufficient, as pointers are process-relative and would make no sense in the recipient’s process.
There are several approaches to implementing serialization that are platform-independent and will support fairly complex objects. One approach is to use your favorite C++ class library’s serialization facilities, whether it be MFC’s CArchive framework, Rogue Wave’s RWStreams, or the iostream library’s ASCII-ization layer. To do this, you typically implement some well-known methods or operators to map your object onto the serialization functions provided by the library. You have to write some serialization routines, and this technique forces your implementation to bring in a fairly large C++ class library (which adds to the size of your executable) by either static linking or forcing the user to install a DLL. Prior to the 28.8bps modem becoming the de facto software distribution mechanism this may have been OK, as CD-ROMs had plenty of space for MFC40.DLL. For code-download scenarios, this approach may increase your overall product size beyond what your users find acceptable.
An alternative approach would be to examine what you wanted in the first place. This discussion started by investigating marshaling issues. It turns out that the proxies and stubs that MIDL generates need to serialize fairly complex parameters into byte streams to send across the network. If only there was a way to leverage MIDL, you could implement serialization without writing any serialization code. Instead, you could simply define your class’s data members in IDL.
It turns out this is possible by using the encoding services of MIDL (colloquially known as pickling), which uses the NDR engine from the RPC runtime layer. Pickling is a technique that asks MIDL to expose the serialization of a data type or procedure as callable C functions. These MIDL-generated functions serialize or deserialize data into a byte stream that is represented by a special type of RPC binding handle. Pickling uses the same Network Data Representation (NDR) routines that are used to serialize method parameters for RPC and COM. The NDR routines encode your data into a canonical representation that can be decoded on any platform. This means that your pickled state is inherently platform-independent. This also exposes the rich functionality of pointer chasing, duplicate detection, and data type aliasing that is available for normal RPC and COM method calls.
To use pickling to serialize an object, you first need to define the data members of the class in IDL as a C-style struct:

 [uuid(00000001-1972-1980-1984-000BABEEFACE)]
 interface IRepresentation
 {
   typedef struct tagMYCLASSREP {
     long m_nAge;
     [string] const char *m_pszName;
   } MYCLASSREP;
 }
Since you are using pickling as an implementation technique and it is not part of the public COM interface to your class, this IDL definition should not appear in the same IDL file that you use to define your COM interfaces. Also, note that the interface IRepresentation does not have a base interface or object attribute. This interface is a pure RPC interface since pickling is part of RPC, not COM.
Once the MIDL compiler generates the C definition of MYCLASSREP, you can bring this into your C++ class either as a data member

 class CMyClass
 {
   MYCLASSREP m_dataMembers;
 };
or as a base class.

 class CMyClass : private tagMYCLASSREP
 {
   
 };
The advantage of the latter approach is that the member names of MYCLASSREP (m_nAge and m_pszName) can be accessed syntactically, as if they were declared as data members of CMyClass explicitly.
To inform the MIDL compiler that you want to generate callable serialization routines for the data type MYCLASSREP, you will need to create an Application Configuration File (ACF). ACF files work in tandem with an IDL file and contain attributes and definitions that affect the generated source code but do not affect the corresponding packet formats. In classic RPC, this is where you specify implicit or explicit binding handles. MIDL assumes that the file name of the ACF file is just the IDL file name with an ACF file extension. If you run MIDL on the file foo.idl, MIDL looks for the file foo.acf in the same directory. If it does not find an ACF file, it assumes that there are no special requirements for the generated sources.
Given the previous IDL fragment, the following ACF interface definition forces MIDL to generate serialization routines for MYCLASSREP:

 []
 interface IRepresentation
 {
   [encode, decode] typedef MYCLASSREP;
 }
When MIDL encounters these ACF attributes, the generated source code will contain three function definitions:

 size_t MYCLASSREP_AlignSize(handle_t hMes, 
                            MYCLASSREP *pType);
 void MYCLASSREP_Encode(handle_t hMes, 
                       MYCLASSREP *pType);
 void MYCLASSREP_Decode(handle_t hMes, 
                       MYCLASSREP *pType);
Each of these routines takes an RPC binding handle as the first parameter. This binding handle can be acquired via one of several API functions, depending on how you want to map the encoding onto the underlying encoding medium. While you can encode into raw memory, this can require more resources; the entire serialized state of the object needs to be written to one giant contiguous buffer. A more efficient technique is to use the incremental encoding handles that allow the programmer to implement each individual read and write operation in terms of the underlying serialization medium (files and sockets, for example). Since IMarshal and IPersistStream both use IStreams as the underlying medium, a small amount of C++ glue can provide a mapping from IStream onto encoding handles. Figure 11shows a C++ class that is useful for encoding and decoding into streams.
Given the class shown in Figure 11, implementing IPersistStream or IMarshal is very straightforward. The implementation of the IPersistStream::Load method in Figure 12 shows how to use this API along with the MIDL-generated Encode routine. The corresponding Save routine simply constructs a decode handle and calls the Encode routine (see Figure 13). These routines are very regular and type-independent, making them easily generalized using either C++ templates or the C macro preprocessor.
One caveat should be noted before you dive into the world of pickling. Pickling is a part of MS-RPC, not COM. As such, several important differences between the two technologies affect how you implement your object. First, the Encode and Decode routines use the RPC rules for memory allocation of all pointers. This means that instead of using CoTaskMemAlloc and CoTaskMemFree, they expect you to write two functions, MIDL_user_allocate and MIDL_user_free, which will be called by the MIDL-generated encoding routines. All memory you allocate for your data members must use these allocation routines as well. Since you get to write these allocator functions, you can do this transparently by implementing MIDL_user_allocate for your C++ compiler’s malloc:

 void * __RPC_USER MIDL_user_allocate(size_t cb)
 { return malloc(cb); }
 void __RPC_USER MIDL_user_free(void *pv)
 { free(pv); }
Second, you should note that the only data types that are encodable via pickling are the classic RPC data types. Beyond eliminating C++ classes, COM interface pointers are not allowed in encodable structures. MIDL will allow them, but the current MS-RPC runtimes will throw an exception when an interface pointer is serialized.
I just presented several techniques that make marshaling objects by value possible in COM. The primary advantage to marshaling by value is that once an object is sent as a method parameter, no further communications are required to access the object’s attributes. This can have a positive impact on performance, especially in a distributed environment. The downside of marshal-by-value is the loss of object identity. Once an object is transmitted by value, no changes that are made to it will be propagated back to the original source object. For immutable, constant objects, this is not a limitation at all. For dynamic objects, this may or may not have the desired effect. If you do implement marshal-by-value for dynamic objects, it’s in your best interests to document this to anyone interested in using your object across process boundaries.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox/default.asp

From the March 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft