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
December 1995


Code for this article: activexcode1295.exe (5KB)
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 OK, I've decided to start living the OLE lifestyle and only allow clients to interact with my objects via interfaces, but it seems that there is more than one type of interface to choose from. What exactly are the tradeoffs and limitations of custom and dispatch-based interfaces? Are dual interfaces the answer?

A One increasingly important use of OLE and COM is to allow arbitrary objects to communicate through user-defined interfaces. This allows OLE-compliant class libraries to maintain release-to-release binary compatibility without requiring weird compiler tricks or undue hardship on the developer. In Cairo, user-defined interfaces will increase in importance as they become the preferred method for writing distributed applications for Windows®. Given the importance of user-defined interfaces, it is useful to explore the choices you must make when designing and implementing COM interfaces. First and foremost among these is the choice between direct vtable-based interfaces (often called custom interfaces) and indirect, dynamic invocation interfaces (often called automation or dispatch interfaces).
Custom interfaces are the simplest to implement and access from C++. Since custom interfaces are based on C++'s concept of an abstract base class, no special coding is required on the client side of the interface: simply acquire the user-defined interface pointer (often via QueryInterface) and invoke member functions using the arrow operator. On the object implementation side, no unusual code is required to implement the user-defined member functions, and the three IUnknown member functions have very standard implementations. Figure 1 shows a simple custom interface definition in C++, and Figure 2 and Figure 3 show a basic interface implementation and client. Except for the use of QueryInterface, the code looks remarkably like traditional C++.
If the interface will only be accessed by the thread that owns the object (that is, the thread that calls the new operator from within the class factory's CreateInstance member function), nothing is needed beyond a simple C++ header file that defines the interface. This is due to the close relationship between COM and C++. However, C++ (like most languages used in commercial software development) does not intrinsically support concurrency, multiple stacks of execution, or interprocess/network communications. This means that if an interface is to be accessed by a thread (or process) that did not create the object, some special technique must be used to invoke member functions such that they are always executed by the thread that owns the object. To enforce this constraint, when interface pointers are passed from one thread to another, the interface pointer must be marshaled into the remote thread's context.
COM supports two marshaling techniques: custom marshaling and standard marshaling. Custom marshaling and custom interfaces are often confused, which is not surprising given that the terms standard marshaling and standard interface are also used quite often. The fact that in the dark ages there was very little documentation on marshaling in general doesn't help the matter. See Figure 4 for a quick summary. While custom marshaling is a very powerful technique for solving a certain class of problems, standard marshaling is a much more general technique and more likely to work over a heterogeneous network. Since I'm looking at the ramifications of various types of interfaces, I'll ignore custom marshaling as it has little effect on which type of interface you choose to implement.
Standard marshaling can best be thought of as "marshal by reference." As shown in Figure 5, no part of the object is transmitted to the client when using standard marshaling; instead, a proxy is instantiated in the client's thread context to act as the client's "reference" to the object. Under standard marshaling, when the first interface of an object is marshaled to a new thread, a system-supplied communication channel is created to allow the proxy to send messages to the remote stub. This communications channel is a COM object that exports the IRpcChannelBuffer interface:


 interface IRpcChannelBuffer : public IUnknown
 {
     HRESULT GetBuffer(RPCOLEMESSAGE *pMessage,
                       REFIID riid);
     HRESULT SendReceive(RPCOLEMESSAGE *pMessage,
                         ULONG *pStatus);
     HRESULT FreeBuffer(RPCOLEMESSAGE *pMessage);
     HRESULT GetDestCtx(DWORD *pdwDestContext,
                        void **ppvDestContext);
     HRESULT IsConnected(void);
 };
The current implementation of the communication channel uses PostMessage to send the message to the object's thread, and will likely use the RPC transport infrastructure to implement network OLE in Cairo. Proxy/stub pairs that communicate exclusively via the channel are more likely to work without modification when COM moves out over the network.
As part of establishing the initial client-object connection, the system instantiates a generic proxy manager that manages the connection to the corresponding generic stub manager in the object's process. By themselves, these generic managers are only capable of supporting the IUnknown interface. To support additional interfaces, each manager needs to instantiate a specific proxy or stub that knows how to remote the additional methods of the interface being marshaled. To implement the proxy for an interface IFoo, the interface designer would need to provide a COM object that supports two interfaces: IRpcProxyBuffer and IFoo. The IRpcProxyBuffer interface provides the proxy manager with a mechanism for establishing and tearing down the connection to the remote object:

 interface IRpcProxyBuffer : public IUnknown
 {
   HRESULT Connect(IRpcChannelBuffer *pChannel);
   void    Disconnect(void);
 };
The proxy's IFoo interface is exposed directly to the client using COM aggregation, and is responsible for marshaling each method's parameters into a packet to be transmitted to the remote side. To implement the stub for an interface IFoo, the interface designer would need to provide a COM object that supports only one interface, IRpcStubBuffer.

 interface IRpcStubBuffer : public IUnknown
 {
     HRESULT Connect(IUnknown *pUnkServer);
     void    Disconnect(void);

     HRESULT Invoke(RPCOLEMESSAGE *pRPCMSG,
                    IRpcChannelBuffer *pChannel);

     IRpcStubBuffer *IsIIDSupported(REFIID riid);
     ULONG   CountRefs(void);

     HRESULT DebugServerQueryInterface(void** ppv);
     void    DebugServerRelease(void *pv);
 };
The Connect member function establishes the connection to the actual object. To execute a member on the remote object, the proxy calls the SendReceive member function on the channel, and the channel transmits the packet to the stub by calling the Invoke member function. It is the responsibility of Invoke to unmarshal the parameters from the packet onto the stack frame and call the appropriate member function on the actual object. When the object method is complete, the result and any outgoing parameters are marshaled into a return packet that is then sent back to the proxy. Upon reception of the return packet, the proxy wakes up from the call to SendReceive and unmarshals the results for the client.
One powerful feature of COM standard marshaling is that the proxy and stub for an interface need to be implemented only once. Once registered with the system, the proxy and stub for an interface are then shared by all classes that support the interface. Perhaps the biggest advantage that standard marshaling has over custom marshaling is that there are tools to produce proxy/stub pairs based on formal definitions of an interface.
You usually formally define an interface in a special definition language. These definition languages are implementation-language neutral and allow additional semantics beyond what is possible in C or C++. These language- neutral descriptions are then sent through a special compiler that produces (among other things) the C and C++ definitions of the interface for use by both the client and object implementation. Unfortunately, there are two definition languages (and compilers) for describing COM interfaces, Interface Definition Language (IDL) and Object Description Language (ODL), and both perform a distinct and necessary function. A merging of the two languages is planned for release sometime after this writing.
The IDL is an extension of the OSF DCE IDL language used to describe and define remote procedure calls. (TLA Alert: Open Software Foundation Distributed Computing Environment—a large full-bodied suite of network services and protocols designed in the 1980s when network transmission speeds were slow and functional programming was still in vogue.) The Win32 SDK (but not Visual C++) includes an IDL compiler (MIDL.EXE) that produces a C/C++ header file containing the definition of the interface, and the C language source code for the proxy and stub used in standard marshaling. As Figure 6 shows, these files can then be sent though the C compiler and linked as a DLL. Once this DLL is installed in the registry, the interface can then be accessed across thread and process boundaries.
Figure 7 shows the IDL description of an interface, the DEF file, and a simple makefile. Once you've made a DLL from these files, you need to install it by adding the following registry keys:

 HEKY_CLASSES_ROOT
   \Interface
     \{BABACACA-DADA-EAEA-FAFA-00000000000B}
       \ProxyStubClsid32
       = "{BABACACA-DADA-EAEA-FAFA-00000000000B}"
   \Interface
     \{BABACACA-DADA-EAEA-FAFA-00000000000B}
       \NumMethods
       = 4
   \CLSID
     \{BABACACA-DADA-EAEA-FAFA-00000000000B}
       \InprocServer32
         = "IFooPS.dll"
The first key tells the standard marshaler the COM class name of the proxy/stub implementation. In this case the IID of the interface is reused as the CLSID of the marshaler. The standard marshaler then uses the CLSID to instantiate the proxy and stub.

Dispatch Interfaces

Custom COM interfaces allow clients to invoke member functions based on knowledge of the argument types and vtable index. In contrast, dispatch interfaces allow clients to invoke member functions using human-readable member names and generic, self-describing parameters. To understand how dispatch interfaces work, it helps to mentally separate the logical interface that is being accessed from the physical interface used to invoke methods. Consider the following custom interface, IBar, as described in IDL:

 [ uuid(00000000-0000-0000-FFFF-112233445566), object]
 interface IBar : IUnknown {
   HRESULT Banter([in] short arg1);
   HRESULT Bicker([out] short *parg1);
   HRESULT Brouhaha([in] long arg1);
   HRESULT Ballyhoo([in] long arg1);
 }
IBar describes a family of logical operations (Banter, Bicker, and so on) that can be invoked on some object. Since IBar is a custom vtable-based interface, the physical interface used to invoke methods is identical to the one that describes the logical interface. There is only one interface.
To allow clients to access the IBar logical interface through a physical Dispatch interface, the object would need to also support the IDispatch interface (see Figure 8). The object's implementation of GetIDsOfNames would need to map the text representations of the member names (strings) to unique integral values (DISPIDs). The implementation of Invoke would then need to use the DISPID to index into a jump table and parse the generic array of VARIANTs contained in pdispparams onto a normal C++ stack frame. The work performed by an implementation of IDispatch::Invoke is not dissimilar to an implementation of IRpcStubBuffer::Invoke, the main difference being that IDispatch::Invoke receives the parameters in a well-defined array of VARIANTARGs, while IRpcStubBuffer::Invoke receives the parameters as a raw stream of bytes.
To allow clients to discover an interface's DISPIDs during the development phase, as well as to automate the implementation of IDispatch, it is useful to describe both the logical and physical interface in a type library. Type libraries act as binary, language-independent descriptions of a collection of data types and can be generated programmatically or by using a type library compiler. The Win32 SDK and Visual C++ both ship with MKTYPLIB.EXE, the standard type library compiler.
MKTYPLIB parses the other COM definition language ODL. ODL is used to describe:
  • Interfaces invoked via vtables
  • Interfaces invoked via IDispatch
  • COM instantiable classes
  • C-style structures
  • C-style enumerations
  • C-style typedefs
ODL is similar to IDL but is not a hundred percent compatible. Here's the ODL description of a logical interface:

 [ uuid(00000001-0000-0000-FFFF-112233445566), odl]
 interface IQuuxLogical : IUnknown {
   [id(1)] void Chatter([in] short arg1);
   [id(2)] short Commentary();
   [id(3)] void Criticism([in] long arg1);
   [id(4)] void ChitChat([in] long arg1);
 }
Note that each member is assigned a unique DISPID, and that the object keyword used in IDL has been replaced with the odl keyword shown above. The two different keywords will hopefully allow the two compilers to someday be merged into one. Note that none of the member functions returns HRESULTs; HRESULT is very COM-specific and is often used to convey infrastructure-level errors (such as communications failure). Since this interface is used to specify the logical operations that an object will support, the return types reflect the logical result of the operation.
The ODL description above describes both a logical interface and its corresponding direct vtable-based physical interface. To indicate that the logical interface is available through IDispatch, the following ODL construct is required:

 [ uuid(00000002-0000-0000-FFFF-112233445566)]
 dispinterface DQuuxPhysical {
   interface IQuuxLogical;
 }
The statement above implies that DQuuxPhysical is derived from IDispatch and will map onto the IQuuxLogical logical interface. Physically, DQuuxPhysical is identical in layout to IDispatch (that is, it adds no new member functions), but because of this ODL statement, it is guaranteed to support the semantics and DISPID bindings of IQuuxLogical.
To indicate that a given COM class supports the DQuuxPhysical interface, the following construct is also required:

 [ uuid(00000003-0000-0000-FFFF-112233445566)]
 coclass CoQuux {
   [default] dispinterface DQuuxPhysical;
 }
The statement above implies that if CoCreateInstance is called using CLSID_CoQuux as the class name, it will return an object that is capable of exporting a DQuuxPhysical interface via QueryInterface. Figure 9 and Figure 10 show the complete ODL listing for CoQuux and the corresponding implementation. The implementation of the IDispatch members was trivial, as the system-provided implementation of the ITypeInfo interface does most of the work, based on information retrieved from the object's type library.
Figure 10 Dispatch Interface
Figure 10 Dispatch Interface

IDispatch is a standard interface and has built-in support for standard marshaling. If you only expose functionality via IDispatch, no additional proxy/stub implementations are required. When the type library is registered using LoadTypeLib, the system will also place entries under \Interface for each dispinterface in the library. These entries simply indicate that the standard marshaler for IDispatch should be used when marshaling the interface.
There are two fundamental disadvantages to using dispatch-based interfaces. First, to simplify the marshaling of IDispatch, its designers made the physical representation of each parameter completely self-describing through the use of the VARIANTARG data structure. VARIANTARGs can contain values, pointers or arrays of one of the 13 primitive data types supported by dispatch interfaces. While the basic integral and floating point types are supported, the following are notoriously absent: unsigned scalar types (unsigned int, unsigned short, unsigned long), non-Unicode text strings, and user-defined structures. The lack of unsigned types is the easiest to work around through the judicious use of typecasting. The lack of support for ANSI text strings can also be worked around by converting all strings to Unicode prior to passing them through Invoke. While this approach is inefficient, it is at least solvable without a major overhaul.
The lack of support for user-defined structures is the biggest problem. You have several options for passing structures across logical method calls.
If the object is guaranteed to be in-process, the client can simply pass a pointer to the structure through a VARIANT by treating the pointer as a long integer; however, in the out-of-process case, the pointer will be about as useful to the object as last week's winning lotto numbers. For an object that is guaranteed to be in-process, such as an OLE control, this approach works, but again, it doesn't apply in the general case. You have been warned.
Another idea is to copy the structure into a SAFEARRAY of bytes (VT_UI1). This is the preferred approach as it is the closest IDispatch comes to a raw unformatted buffer and is guaranteed to work in the out-of-process case. Once network OLE is supported on non-Intel platforms, you must be careful to parse the structure correctly, taking into consideration byte-order and alignment issues.
A third idea is to create a new COM object that contains the structure and supports the IDataObject interface. This approach assumes that the other side is capable of QI'ing for IDataObject and knows what format to use when calling GetData. This approach would require one additional out-of-process call to get the data. Again, the platform-dependencies that will be encountered when using the SAFEARRAY approach apply here as well.
Yet another method would be to create a new COM object that contains the structure and exposes an IDispatch interface that maps the structure's data members to logical properties. This approach is by far the easiest for dispatch-only clients to deal with, but in the out-of-process case, requires one additional out-of-process call for each structure member that is exposed as a property.
The final approach: instead of trying to pass the structure directly, pass each structure member as a separate method parameter. This requires a bit more work on both the client and object sides, but certainly works. For data structures with a small number of parameters, this is a reasonable approach.
The upshot? Dispatch interfaces restrict the types of parameters you can use in a logical interface. Venturing beyond the basic 13 types is a pain.
The second major drawback to dispatch interfaces is decreased performance. As is shown in Figure 11, processing an in-process Invoke call can cost a considerable amount of CPU time, up to 275 times the cost of making a direct vtable-style call. In the out-of-process case, the cost of context switching swamps the incremental overhead of Invoke processing, reducing its penalty relative to custom interfaces. The penalty for resolving member names to DISPIDs at run time is severe in both the in-process and out-of-process cases, as roughly twice as much work must be performed (that is, two round-trips are required). Unfortunately, this was the only technique available under Visual Basic 3.0. Visual Basic for Applications and Visual Basic 4.0 allow the client to prebind the DISPID based on information contained in the type library.
Be careful when interpreting the results in Figure 11. First, the timing only reflects method invocation cost. The test object performed virtually no work in each method invocation. Except for Get/Set members, this will probably not be the case for most "real" objects. Second, note that in-process dispatch calls are considerably faster than out-of-process vtable-based calls. If you can tolerate the lack of robustness inherent in sharing an address space, you should seriously consider building your objects as in-process servers.

Dual Interfaces

If a client is smart enough to prebind the DISPIDs of an IDispatch interface, avoiding the cost of IDispatch::GetIDsOfNames, why can't even smarter clients bind directly to a vtable offset, avoiding the cost of IDispatch::Invoke? This is the motivation behind dual interfaces.
Dual interfaces are a hybrid of dispatch and custom interfaces. A dual interface has IDispatch as its base interface and implements the logical interface in the member functions beyond the initial seven IDispatch/IUnknown members. One advantage of dual interfaces over custom interfaces is that the system provides a universal proxy/stub implementation that can marshal the interface based on the information contained in a type library. This means that, like normal dispatch interfaces, if you provide a type library, you get standard marshaling support without implementing a proxy/stub pair. Clients that are sufficiently smart (any C++ client or Visual Basic 4.0, for example) can get the efficiency of a custom interface when accessing in-process objects, as the IDispatch members are not used. Unlike custom interfaces, however, if the client is not smart enough to access the direct vtable entries, the IDispatch members are available as well.
Specifying a dual interface in ODL is actually simpler than in the normal IDispatch case. The following describes a dual interface that can be accessed either indirectly via IDispatch::Invoke or directly through a vtable:

 [ uuid(00000005-0000-0000-FFFF-112233445566), 
   odl, dual]
 interface DIQuux : IDispatch {
   [id(1)] HRESULT Dialog([in] short arg1);
   [id(2)] HRESULT Dissension([out, retval] 
                               short *pResult);
   [id(3)] HRESULT Debate([in] long arg1);
   [id(4)] HRESULT Diatribe([in] long arg1);
 }
The physical result of each member is an HRESULT. This is required to allow the implementation to communicate the presence of dispatch-based exceptions or other abnormal conditions. To support logical results, ODL was extended to support passing the logical result by reference as the last parameter of the function, as is shown in the Dissension method above. One additional benefit of Dual interfaces is that the second dispinterface construct is no longer required (it is implied by the dual keyword). This also reduces the number of interfaces that must be supported by the object.
Figure 13 Dual Interface
Figure 13 Dual Interface

Figure 12 and Figure 13 show the implementation of the dual interface described above. Notice how the implementation becomes simpler, not more complex. Figure 14 shows the performance of dual interfaces. For in-process objects, the performance is identical to that of custom interfaces when using integral types, and only marginally slower when passing Unicode strings. Since dual interfaces suffer from the same data-type restrictions as dispatch interfaces, if ANSI text strings are to be used as parameters, they must be converted to Unicode prior to being sent to the object and converted back to ANSI on the receiving end. When communicating with out-of-process objects, dual interfaces perform marginally slower than custom interfaces due to the table-driven marshaling. If this additional overhead is an issue, the ODL description of the interface can be converted to IDL to produce a more exact marshaler, bringing the performance up to the level of a custom interface. For most developers, this is probably not worth the effort.
So, after all that, what sort of interfaces should you use? Figure 15 sums up the issues relating to each type of interface in handy chart form.
Clearly, supporting dispatch or dual interfaces allows virtually anyone to easily access your objects. Custom interfaces assume predominantly C++ or C clients, and are painful to access from Visual Basic.
Dual interfaces subsume traditional dispatch interfaces. If you decide to support IDispatch, implementing it using a dual interface will give you considerably better performance when interacting with C++ or Visual Basic 4.0 clients, especially if you are an in-process object.
If you cannot simplify your argument types enough to fit within the confines of the VARIANT, go for custom interfaces. Be prepared to learn IDL beyond the basics if you plan to allow unrestricted use of C-style pointers and arrays and want your interface to support standard marshaling.
One of the most elegant features of COM is that you don't have to decide. QueryInterface lets you support all three types of interfaces, allowing clients to ask for as particular an interface as they deem necessary.

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 December 1995 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe .

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

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