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 > February 1997
February 1997


George Shepherd develops mapping software for Orbital Software and delivers training courses for DevelopMentor. Scot Wingo is cofounder of Stingray Software, producing MFC extension class libraries. George and Scot wrote MFC Internals (Addison-Wesley, 1996).

It isn't enough any more to simply expose your objects using either COM interfaces or dispatch interfaces. Dual interfaces are now de riguer! Microsoft recommends using dual interfaces wherever possible so your clients have more flexibility in talking to your COM objects. Dual interfaces are fairly straightforward if you develop your COM objects using raw C++ or the ActiveX Template Library. Writing dual interfaces is more complicated if you're creating a COM object with MFC. For example, MFC remains the easiest and most efficient way of producing ActiveX controls: AppWizard generates one for you in a matter of seconds. Yet, by default, MFC-based ActiveX controls implement only a plain-vanilla IDispatch (the one you build up using ClassWizard's Automation tab).
What if you want to expose that IDispatch interface as a dual interface for clients that understand dual interfaces? All it takes is a bit of exploring MFC, learning about Object Description Language (ODL), and your ability to exercise the TextWizard. Some of this information is covered in MFC Tech Note 65, though there are some pieces missing (which we'll fill in here, of course).

Dual Interfaces Aplenty

COM supports three types of interfaces: pure COM, dispatch, and dual. Pure COM interfaces are similar to C++ vtbls. In fact, pure COM interfaces are defined in C++ as pure abstract base classes. Clients of COM objects that expose COM interfaces understand how to talk to the object because the COM interface is specified in a header file. The COM client sees the header file and is immediately aware of the function signatures and the order in which the functions appear in the table. Clients access a pure COM interface with the same syntax they use for a plain-vanilla C++ class.
For example, given the following interface definition


 // ICOOL.H
 struct ICoolInterface : public IUnknown {
    virtual HRESULT Function1() = 0;
    virtual HRESULT Function2() = 0;
 };
 
 // {49BA7C02-40D4-11d0-8A90-E1C000C3FC20}
 DEFINE_GUID(IID_ICoolInterface, 
             0x49ba7c02, 0x40d4, 0x11d0, 0x8a, 0x90,  
             0xe1, 0xc0, 0x0, 0xc3, 0xfc, 0x20);
a COM client can exercise the interface using the following code:

 //USECOOL.CPP
 #include "icool.h"
 
 ICoolInterface* pCool;
 
 void UseCoolInterface() {
    if(SUCCEEDED(CoCreateInstance(CLSID_CoCoolClass,
                                  NULL,
                                  CLSCTX_ALL,
                                  IID_ICoolInterface,
                                  (void**)pCool)) {
       pCool->Function1();
       pCool->Function2();
    }
 }
Notice that the COM interface approach involves pointers. The COM client acquires a pointer to an interface (not the object itself) and uses the interface definition to call the functions in the interface. While this is a great way for C++ clients to access functionality, it's a big problem for environments like Visual Basic®, which don't support pointers in the same manner. The way to get Visual Basic programmers into the game is to introduce a single well-known interface that provides a way to get the same functionality. COM provides such an interface and it's called IDispatch.

 interface IDispatch : public IUnknown {
    virtual HRESULT GetTypeInfoCount(UINT FAR* pctinfo) = 0;
    virtual HRESULT GetTypeInfo (UINT itinfo, LCID lcid,
                                 ITypeInfo FAR* FAR*
                                 pptinfo) = 0;
    virtual HRESULT GetIDsOfNames(REFIID riid, 
                              char FAR* FAR* rgszNames,
                              UINT cNames, LCID lcid,
                              DISPID FAR* rgdispid) = 0;
    virtual HRESULT Invoke(DISPID dispidMember,
                           REFIID riid, LCID lcid,
                           WORD wFlags, 
                           DISPPARAMS FAR* pdispparams,
                           VARIANT FAR* pvarResult,
                           EXCEPINFO FAR* pexcepinfo,
                           UINT FAR* puArgErr) = 0;
 };
IDispatch involves a tokenized lookup mechanism instead of a pointer-based mechanism. Once automation clients acquire the IDispatch interface, they can use IDispatch::Invoke to carry out their bidding. Notice Invoke's first parameter, a DISPID. DISPIDs are simply tokens (32-bit numbers) defined by the automation object that the client uses to invoke the object's functions and properties. For example, if a client discovers that an automation object implements a function called DoSomething, the client can find the token representing DoSomething by either calling IDispatch::GetIDsOfNames or by consulting the object's TypeInfo. Once the client acquires the token, the client uses the token to call IDispatch::Invoke, avoiding the need to understand function signatures and function table layout information.
As you can imagine, getting to an object through IDispatch isn't the most efficient proposition. Clients need to set up IDispatch::Invoke by packing up arguments into variant structures and setting up the flag to indicate the kind of call (setting a property, getting a property, or calling a method). Once the client makes the call, the object has to decipher all the parameters, unpack the variants, and perform the actual operation. Then the client has to pack up the result in a variant and return it. Finally, the client needs to unpack the returned variant and check out any errors or exceptions. All this takes time. If you're writing a C++ program and you want to use a COM object, you'd get better performance if you went straight for the COM interface. In addition, COM interfaces are much easier to use in C++. If a COM object supports only IDispatch, the client is stuck with a performance hit. If you want to improve performance for your COM interface clients, you need to implement a completely separate interface.
Enter dual interfaces. The best of both worlds, they're COM interfaces and dispatch interfaces rolled into one. Here's a great way to think about dual interfaces: you know that every COM object starts with IUnknown (that is, QueryInterface, AddRef, and Release)—that way clients can count on certain functionality always being available. The object provides a way of maintaining a reference count and the client has a way to try to get more interfaces when it needs to. Like COM interfaces, dual interfaces always start off with an established set of functions, like QueryInterface, AddRef, and Release, and continue with the IDispatch functions (GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke), finishing with the functions that make that interface unique.
To COM clients, a dual interface looks like any other COM interface. Clients of dual interfaces that understand COM interfaces can focus on the first three functions (IUnknown) and the functions following IDispatch, completely ignoring the IDispatch functions. For clients that don't understand pure COM interfaces, the dual interface looks like just another implementation of IDispatch, and a client can use the interface that way. Figure 1 illustrates how this works.
Figure 1  ICoolInterface Exposed as a Dual Interface
Figure 1 ICoolInterface Exposed as a Dual Interface

Dispatch interfaces are at the heart of ActiveX controls. Although MFC remains the easiest way to write ActiveX controls, adding dual interfaces to MFC-based ActiveX controls is a somewhat roundabout process. You have to understand how MFC implements COM and how to work with ODL.
The way to COM using MFC involves macros to assemble the interfaces. You implement COM classes in MFC using a variation on the class-composition approach. Two sets of macros are needed: one to define and implement the nested classes for implementing the COM interfaces, and another to declare and implement MFC's version of QueryInterface. This is what's happening under the sheets to implement IDispatch (it's just another COM interface, you know).
Giving substance to a particular instance of IDispatch involves the ClassWizard. With ClassWizard, you can whip up in minutes an implementation of IDispatch that might take a C++ developer hours to implement by hand. It's almost exactly the same as using ClassWizard to trap messages in your app. Simply press the Property button in the Automation tab to add a property to your IDispatch interface and press the Method button to add a function to your IDispatch implementation.
Another thing ClassWizard does for you is maintain the ODL file. Doing any serious work with automation requires you become familiar with ODL. ODL is an entirely declarative language that describes type libraries, COM interfaces, dispatch interfaces, COM classes, and dual interfaces.
Turning on the automation check box causes AppWizard to pump out an ODL file for your project. ActiveX controls get ODL files automatically, which will have the same name as your project with an ODL extension. Figure 2 describes an ActiveX control in ODL with three properties (AProperty, PlainVanillaProperty, and NotificationProperty) and two methods (AFunction and AboutBox).
This ODL file describes a collection of attributes (whatever's inside square brackets) and "things" in this module (which happens to be an ActiveX control). ODL always follows an "attribute-thing" format. Attributes appear in the square brackets before the things. This type library consists of two dispatch interface descriptions (_DDualCtl and _DDualCtlEvents) and the description of a COM class (DualCtl). The control implements _DDualCtl and control containers implement _DDualCtlEvents. Implementing dual interfaces in MFC requires modifying the ODL file, so you'll have a chance to become intimately familiar with the syntax.

Doing the Dual

Adding a dual interface to your MFC-based ActiveX control requires five steps:

  • Create your control skeleton using AppWizard.
  • Add your properties and methods to the control.
  • Modify the control's ODL file to describe the dual interface you want to support.
  • Add the corresponding interface to your control.
  • Add type library registration support to your control.
Let's implement a dual interface for the ActiveX control called DualCtl whose ODL appears in Figure 2. It's a regular ActiveX control, except that it'll expose its properties and methods through a dual interface.
Let's start off by creating the control. Select File New, select the options you want for your control, and let AppWizard do its thing! In seconds you've got a full-blown MFC-based ActiveX control waiting to do your bidding. Next, use ClassWizard to add the properties and methods that make your control unique. Once the control is finished, you need to add properties and methods to the control.
ClassWizard makes this step easy, too. The only caveat here is that you should think hard about the properties and methods you want to expose. If you mess something up here, changing the code later becomes more difficult because you have to modify a lot more of it (no more just selecting Delete from the ClassWizard's Automation tab). Anyway, once you settle on your incoming IDispatch interface (that is, your control's methods and properties), it's time to move on to the ODL file.
To add a dual interface to the control, you've got to get in and modify the ODL file (despite the scary warnings amid the ClassWizard comments). The first thing to do is add another interface description—one for the dual interface. Given the ODL from Figure 2, you need to add the lines shown in Figure 3 to the ODL code.
There are several things going on that might strike you as odd. Why do you need to add another interface definition? If you go back and change the AppWizard-generated ODL file to add the new keywords, there's a good chance you'll break ClassWizard. Adding a completely separate interface avoids this problem.
What's all this propput and propget stuff in Figure 3? The original AppWizard-generated ODL uses the keywords "properties" and "methods" to delineate the object's properties and methods. This is legal ODL syntax (albeit a bit outdated now). To define this interface as a dual interface, you need to define each property into separate getxxx and putxxx functions. Applying the keywords propput and propget to the property attributes does the job. Because the COM interface can define only functions, each property has to be represented by separate getxxx and setxxx functions.
Remember that ODL always takes an attributes-thing form. This interface (_DIDualCtl) has a name identified by the uuid statement in the attributes section. In addition to a comment describing the interface, there are two new keywords: dual and interface. The dual keyword tells the type library compiler the interface is a combination of IDispatch and some other functions (that is, the first seven entries in the interface are IDispatch and the next entries are something else). The interface keyword tells the type library compiler the interface is a regular COM interface. ODL is very precise—each function must specify the direction of its parameters (notice the in and out attributes for the parameters). Also notice that the interface has a new globally unique ID (GUID). Because it's a new unique interface, the rules of COM stipulate that it gets a new name.
The final modification to the ODL file is to add the dual interface to the coclass section of the ODL. The default attributes makes the dual interface the primary incoming interface.

 [ uuid(6E281364-40AF-11D0-8A90-E1C000C3FC20),
   helpstring("DualCtl Control"), control ]
 coclass DualCtl {
       [default] interface _DIDualCtl;
       [default, source] dispinterface _DDualCtlEvents;
       dispinterface _DDualCtl;
 };
Once the ODL file defines the new interface, the next step is to graft the new interface to your control using MFC's nested class and interface map mechanisms.

Implementing the Dual Interface

The first step to adding the dual interface to the control is to add the DECLARE_INTERFACE_MAP macro to the control's header file. DECLARE_INTERFACE_MAP sets up MFC's QueryInterface machinery to work with the control. Then you need to add a nested class that implements the dual interface. In the ODL example above, the interface is already fully described. The trick is to turn that interface description into something you can use in this C++ code. Fortunately, the type library compiler is smart enough to read the interface description in the ODL file and turn it into a C++ header file. Here are the steps necessary to do that:

  • Select Build Settings from the menu.
  • Go to the OLE Types tab.
  • Expand the tree in the settings list and highlight the control's ODL file.
  • Type the name of your header file in the "Output header file name" field. (Call it something other than your control's header file. In the example above, the control is named "DualCtl." The interface is defined in a file called IDUALCTL.H.)
  • Load the ODL file in the environment compile it.
Given the ODL file in Figure 2 with the modification above, Figure 4 shows the header file spit out by the type library compiler. This rather scary looking C++ code is exactly what your control needs to implement the dual interface. In addition to the code emitted from the type library compiler, you'll need a macro that comes with the MFC samples. The file, MFCDUAL.H, is in \MSDEV\SAMPLES\MFC\OLE\ ACDUAL\SERVER. This header file contains the macros for defining the nested classes. MFC's normal BEGIN_ INTERFACEPART/END_INTERFACE_PART macros work too, but you have to prototype the four IDispatch functions yourself. BEGIN_DUAL_INTERFACE_PART/END_DUAL_INTERFACE_PART already do that for you.
To add the nested class for implementing the dual interface, simply use the macros as shown in Figure 5. You can more or less steal the code from the header file emitted by MKTYPLIB. Make sure to get rid of the PURE symbol following each function. This gives the control a nested class that implements the dual interface. The control here, DualCtl, is implemented in a class called CDualCtlCtrl. The macros generate a nested class called X_DIDualCtlObj within CDualCtlCtrl that implements the _DIDualCtl interface. _DIDualCtl is declared in the header file generated by the type library compiler.
The next step is to implement the new interface. This is pretty much like implementing a new COM interface to a control except that you need to implement the IDispatch functions as well as the IUnknown functions. Figure 6 shows implementing the IUnknown and the IDispatch functions.
Implementing the IUnknown functions is a matter of using the METHOD_PROLOGUE macro to get a pointer back to the control and delegating to CCmdTarget's IUnknown functions. Implementing IDispatch is a matter of getting MFC's implementation of IDispatch (available through CCmdTarget::GetIDispatch) and delegating to MFC's implementation.
Next comes mapping the dual interface functions to the implementations provided by ClassWizard. Doing this involves getting a pointer back to the control and delegating to those ClassWizard functions. The dual interface methods are also forwarded to the ClassWizard-generated functions, as shown in Figure 7. The thing to watch out for here is that all the dual interface functions return HRESULTs and that results are passed back through out parameters. Figure 7 shows how to implement get_AProperty, set_AProperty, and AFunction.
The next step is to fill in the interface map so that the framework knows where to find your new interface when a client calls QueryInterface. Simply add the entries to the control's interface map as shown below. There are three entries: one for plain old IDispatch, one for a version of IDispatch that's named in the ODL file, and the new custom interface. MFC uses this table to match interface IDs to interface pointers when clients call QueryInterface.

 BEGIN_INTERFACE_MAP(CDualCtlCtrl, COleControl)
 // Plain vanilla IDispatch
    INTERFACE_PART(CDualCtlCtrl, IID_IDispatch, Dispatch)
 // Control's IDispatch
    INTERFACE_PART(CDualCtlCtrl, DIID__DDualCtl,
                   Dispatch)
 // Control's Dual interface
    INTERFACE_PART(CDualCtlCtrl, IID__DIDualCtl,
                   _DIDualCtlObj)
 END_INTERFACE_MAP()
Because type information is such an important part of ActiveX controls, the code generated by AppWizard already includes the code necessary to register type information in the registry. Here's the code that appears in DUALCTL.H:

 STDAPI DllRegisterServer(void)
 {
    AFX_MANAGE_STATE(_afxModuleAddrThis);
 
    if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(),
                               _tlid))
       return ResultFromScode(SELFREG_E_TYPELIB);
 
    if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
       return ResultFromScode(SELFREG_E_CLASS);
 
    return NOERROR;
 }
There are two final caveats. First, you need to include initguid.h in your control's CPP file to give the GUIDs declared in the DEFINE_GUID macro some substance. Second, be sure to include the ODL-generated header file in your control's main source file so the compiler understands the new dual interface (that is, include IDUALCTL.H in DUALCTLCTRL.CPP in this example).

Conclusion

Now you know how to add a dual interface to an ActiveX control. This example shows the importance of understanding COM at the visceral level. Knowing how IDispatch works and how to take advantage of MFC's nested class macros is key to getting a dual interface (or any other COM interface) grafted onto your control. While for now adding a dual interface to an MFC-based ActiveX control isn't the easiest thing to do, it's not impossible. And you can use this technique to add a dual interface to any CCmdTarget-derived class.

Have a question about programming in Visual Basic, Visual FoxPro, Microsoft Access, Office or stuff like that? Sent it to Scot Wingo at visual_developer@stingsoft.com or to George Shepherd at 70023.1000@compuserve.com.

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

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

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