Dr. GUI and ATL

Dr. GUI Online
Part 1: September 30, 1998
Part 2: November 23, 1998

Contents

Part 1: A Very Simple ATL Component
Part 2: Components with Multiple Interfaces

Part 1: A Very Simple ATL Component

Click to open or copy the BeepCnt sample files discussed in this article.

Where We've Been; Where We're Going

Back in February (that's 1998, but it almost seems longer), Dr. GUI set off to start a set of columns on the Active Template Library (ATL).

But the good doctor knew full well that not everyone knew how to use COM, and not everyone knew how to use C++ templates. So he decided to do a column or two to discuss COM and templates and get everyone up to speed.

Well, you all know what happened: one or two columns turned into eight or nine. Specifically, there were eight columns on COM and one long one on C++ templates.

But now that Dr. GUI has talked about everything it's possible to talk about without mentioning ATL, it's time to dig into ATL … so here goes!

ATL Components

What's ATL About?

A brief history

ATL was originally designed as a way to write fast, small COM components. It was especially intended for Automation components that could, for instance, implement business rules and database access in a multitier architecture.

In its first version, ATL did not have any facilities for any sort of user interface—ATL 1.0 controls could not be visual ActiveX controls, for instance (not without almost as much work as implementing it in C++ without ATL, that is). Version 2.0 added the templates necessary to build visual ActiveX controls. We'll discuss ActiveX controls at a later date.

Features

ATL gives you several important features, including:

This last point is probably the most important: One of the hardest things about writing an ActiveX control in unaided C++ is that you have to write an implementation for every method of every interface you implement—and for a visual ActiveX control, that's more than a dozen interfaces! None of these methods have anything to do with the problem you're trying to solve, but you have to write these nontrivial methods correctly anyway.

Alternatives

That's why MFC, Visual Basic, Visual J++, Visual FoxPro, and Delphi are so popular for writing ActiveX controls: their run times implement all the methods of the dozen or so interfaces for you, allowing you to concentrate on the problem at hand.

But using these techniques to implement your ActiveX controls is a Faustian bargain: the run times for all of these products are, to one degree or another, big and slow. If you need your controls to be small and fast, you'll either implement them in C++ directly or you'll use ATL.

The need for speed—with ease

How does ATL give you the leanest, meanest code around? First off, it relies on no run-time libraries at all—so the only time your control needs to use a run-time library is if your code makes use of it. Second, through the magic of templates, ATL controls contain only the code they actually need. So ATL controls are very comparable in size and speed to controls hand-coded by COM experts in C++. But they're far easier to write.

Someone else's code

Dean McCrory, one of the key developers of Microsoft Foundation Classes, has often said that one of the big advantages of using MFC is that you get to reuse code that you didn't write or debug—someone else's code. So MFC gives you print preview virtually for free—that's several thousand lines of code you don't have to write. And it makes it easy to write OLE in-place editing servers. That's many thousands of lines you don't have to write.

That code is especially valuable when it's been tested and debugged. Now, no library is perfect. But the code in a mature library has been used and debugged by many, many programmers—and, as good as you are, the libraries are likely to be faster and more robust than you have time to write yourself. They basically encapsulate all of the developer's expert knowledge and allow you to stand on their shoulders by reusing it.

Templates: A different sort of reuse

Template libraries represent a different type of reuse than function libraries or class libraries. First, reusing templates means reusing source code directly. That means that you can turn the optimizer loose on your programs and make it more efficient. It also means that, should a bug be discovered in a template library, you can fix the bug by modifying the template source. You don't have to rebuild MFC or the CRT and distribute new copies. Just build the next version of your projects with the new templates and the bug will be no more.

If you don't mind redistributing your source code, template libraries provide the best of both worlds: the ability to reuse someone else's code with efficiency about as good as if you wrote everything yourself.

Our ATL Component

Our component for this installment will be very simple: a component that beeps when you call its Beep method. But, to keep things interesting, we'll add a property, too—the number of times that the component should beep when Beep is called.

Since this component is beeping the speaker, it's intrinsically single-threaded—so we'll generate it as such. But we'll give it a dual interface so that it can be used from any COM component client, even a scripting client.

Two-stepping to create the module and its components

Since a module (a DLL or EXE) can implement more than one component, the ATL Wizards break the component creation process in the integrated development environment (IDE) up into two steps. First, you create the module, and then you add the components into the module.

Creating the Module

Creating the module is almost trivial: we select New… from the File menu, and then select the Projects tab and fill in the directory and project name. (Make sure you don't pick the name of the control now—Dr. GUI usually puts "Mod" at the end of the name to make sure.) Your project dialog box will look something like the following. (By the way, Dr. GUI thinks it's no accident that "ATL COM AppWizard" is the default—or could it just be first alphabetically?)

When you click OK, you'll get the shortest wizard you've ever seen.

(Dr. GUI especially likes the "Step 1 of 1" part.)

The main choice here is the type of module—DLL (for in-process server), EXE (for out-of-process server), or NT Service EXE. We'll pick in-process (DLL).

We'll ignore the last three check boxes for now, since we're not using MFC, MTS, or even proxy/stub code.

When we click Finish, we get a project that contains a group of files, which you can find in the "Step 0" subdirectory of your sample directory:

What's inside the module.CPP file

First in this file are the includes and the declaration of a global object and a map:

#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "BeepCntMod.h"
#include "BeepCntMod_i.c"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

We've seen stdafx.h and resource.h already. The file initguid.h is a standard OLE system file you include in exactly one file in your project so the globally unique identifier (GUID) structures are defined.

BeepCntMod.h and BeepCntMod_i.c are generated by Microsoft Interface Definition Language (MIDL) from the IDL file when we build the project. Because MIDL runs before the C++ compiler does, the files will be created in plenty of time for the build.

BeepCntMod.h contains the declarations of the interfaces and components. At this point, it's a pretty boring file. BeepCntMod_i.c is almost as boring: It contains the definitions of the GUIDs we're using.

Next is a global declaration of a variable called _Module. This object contains the class object (class factory) plus all of the other module-oriented code, such as registration of classes, and so on. Check out the documentation on CComModule in the MSDN Library Online for details.

Last for this section is an empty object map. The object wizard will fill this map in. Each element of the map array will contain a CLSID and a C++ class name. The _Module object reads this map so it can create objects based on their CLSID.

Next we have the DllMain function. It simply calls Init and Term functions in the _Module object:

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, 
                    LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_BEEPCNTMODLib);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;    // ok
}

Notice that DllMain also calls DisableThreadLibraryCalls so the DLL won't get a call each time a new thread attaches it.

Next we have the four functions COM DLLs must have:

STDAPI DllCanUnloadNow(void)
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDAPI DllRegisterServer(void)
{
    // registers object, typelib and all interfaces in typelib
    return _Module.RegisterServer(TRUE);
}

STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}

These functions do nothing except call corresponding functions in the _Module object. So it's the _Module object that knows how to register, unregister, tell whether it's okay to unload, and return a class object (part of the object instantiation process).

To find out what these functions do, check the docs—and look at the ATL source. Dr. GUI's best friend when digging through the sources is the GREP-like "Find in Files" feature of Visual C++.

Creating the Object

Now that we understand the module object a little, let's add a COM component to it.

The easiest way to add a COM component is to use the ATL Object Wizard. Just select New ATL Object… from the Insert menu, and you'll get a dialog box that looks like the following.

Select Simple Object here. When you click Next, you'll get a property sheet that allows you to set the names of the object and to set its attributes. Here, type BeepCnt (for "beeper with count") in the Short Name edit control; the wizard fills in the rest. You can change the edit controls if you like.

Clicking on the Attributes tab gives you the following window. All these defaults are fine, so we'll click OK.

"Apartment" threading model means that the object can be used by more than one thread, but COM will ensure that only the thread that created the object will ever call methods on the object. In essence, each instance of the object is single-threaded. Note that you could have multiple objects created on different threads, so if you use any global data in your methods, you'll have to make accesses to it thread-safe. This usually isn't a big deal since you generally only use instance data for objects, anyway. If you do use global data, you need to either make it thread-safe or select single-threaded on this box.

In Apartment Model objects, the class factory has to be multithread safe, but not the object itself. Since ATL implements the class factory for us, we don't have to worry about it—ATL will do the right stuff for us.

We'll use a dual interface for this object. Without going into great detail, that means that the object can be called via both the custom interface (like the ones developed in the COM series) and via the Automation, or IDispatch, interface. Scripting languages use Automation interfaces only, so selecting "Dual" allows our object to be used from a scripting language. Note that the performance is much better when using a custom interface, so if your client can use it (most can), it should.

Aggregation is a special way of containing (or nesting) objects inside other objects. Aggregation is normally quite tricky, but ATL handles all the details for us. Our object is more flexible if it's aggregated, so it's best to leave "Yes" selected.

ISupportErrorInfo is for extended error information used mostly by Visual Basic. We'll skip it, as well as connection points, which are used primarily for events. We'll also pass on the free-threaded marshaller—it's used for certain types of multithreaded controls.

Note something interesting: Not only is our COM object going to be easier to develop, it's also going to be more powerful. Because we're using someone else's ATL code, we get support for apartment model, dual interface, and aggregation for free.

What's inside the object

The ATL Object Wizard added several files to our project and made changes to several others. (By the way, the WinDiff program that comes with Visual Studio is a godsend for figuring out how wizards change files: Just save the "before" files to a separate directory, run the new wizard, and then use WinDiff to see what changed.)

If you've downloaded the sample file, you can find the files for this section in the "Step 1" subdirectory of your sample directory.

The ATL Object Wizard added implementation and header files for the object, beepcnt.cpp and beepcnt.h. The beepcnt.cpp file is almost empty (since we've implemented neither methods nor properties as yet). It will contain the functions that implement the methods and properties we define later. The beepcnt.h file contains the declaration of our component's implementation class:

class ATL_NO_VTABLE CBeepCnt : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CBeepCnt, &CLSID_BeepCnt>,
   public IDispatchImpl<IBeepCnt, &IID_IBeepCnt, &LIBID_BEEPCNTMODLib>
{
public:
   CBeepCnt()
   {
   }

DECLARE_REGISTRY_RESOURCEID(IDR_BEEPCNT)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CBeepCnt)
   COM_INTERFACE_ENTRY(IBeepCnt)
   COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IBeepCnt
public:
};

Already, we're using templates: Our component class derives from not one but three templated classes. CComObjectRootEx handles reference counting for the object. CComCoClass deals with class factory issues. The most interesting templated class is IDispatchImpl, which provides a dual interface based on IBeepCnt, which has IID_IBeepCnt as its interface ID. (If we used a custom interface, we'd derive from IBeepCnt directly.)

The class also contains a number of macros. ATL_NO_VTABLE tells the compiler to not construct a vtable for this class. This can only be used on classes that are not themselves instantiated. But if CBeepCnt is the implementation of our object, how can we not directly instantiate it?

The answer is that ATL derives a class from CBeepCnt and instantiates that. CBeepCnt doesn't need a vtable because no objects of exact type CBeepCnt will ever be created, so we can save memory by using ATL_NO_VTABLE.

But what is this about another object type? Well, the object created will actually be of type CComObject<CBeepCnt>—in other words, we'll use the CComObject template class with our object type as a parameter. A derivation diagram describing this follows.

Why is this done? Well, the primary job of CComObject is to provide implementations of the IUnknown methods. These must be provided in the most-derived class so that the implementations can be shared by all of the interfaces that derive from IUnknown. Note that CComObject's methods just call the implementations in CComObjectRootEx.

As you've likely guessed, a common ATL error is to attempt to create an object by calling new on your implementation class (CBeepCnt, in our case). This fails with strange errors. To create an object, use CoCreateInstance.

The next thing to note is the COM map, which contains macros for the two interfaces we implement—our custom interface and IDispatch, the Automation interface. We do not include an entry for IUnknown here because it's assumed.

There are also a couple of other macros to associate a resource ID with the registry entry (more on this next) and to change the way the object is constructed to make sure it's not accidentally deleted.

The final new file, beepcnt.rgs, contains the source for the script for ATL's registry manipulation code. Most of it corresponds exactly to the registry entries that must be made so the COM run time can find the control. For our simple control, it looks like this:

HKCR
{
   BeepCntMod.BeepCnt.1 = s 'BeepCnt Class'
   {
      CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}'
   }
   BeepCntMod.BeepCnt = s 'BeepCnt Class'
   {
      CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}'
      CurVer = s 'BeepCntMod.BeepCnt.1'
   }
   NoRemove CLSID
   {
      ForceRemove {AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF} = 
                                          s 'BeepCnt Class'
      {
         ProgID = s 'BeepCntMod.BeepCnt.1'
         VersionIndependentProgID = s 'BeepCntMod.BeepCnt'
         ForceRemove 'Programmable'
         InprocServer32 = s '%MODULE%'
         {
            val ThreadingModel = s 'Apartment'
         }
         'TypeLib' = s '{170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF}'
      }
   }
}

This script is used for both registering and unregistering the component. By default when registering, all of the keys are added to whatever keys might already be in the registry. The ForceRemove keyword modifies this behavior so that the key to which ForceRemove is applied is removed (including subkeys) before it's added back in.

When unregistering, the default is to remove every key (and subkey of those) that's listed in the script. It's crucial to override this for the CLSID key by using the NoRemove keyword, as above. If you didn't, unregistering this component would remove the entire CLSID tree, thereby unregistering every COM object on the user's system. So be very careful if you edit the registry script file.

Normally, though, you don't have to edit this file—the wizards will do it for you as you add objects and interfaces.

The resource compiler builds this script into the components resource section, where it's available by ID (in this case, IDR_BEEPCNT).

There are also a few changes to some of the existing files:

When we build, MIDL generates a new BeepCntMod.h to reflect the new object.

Adding methods and properties

Now that we understand what's in our ATL component, adding a method and a property is easy. (Sure, we could do it without digging deeper, but that wouldn't be much fun, would it?)

To add a method, switch to Class View and right-click the IBeepCnt interface connector. Select Add Method… and fill in the dialog box as follows.

Adding the method in this way adds all the necessary code to the IDL file as well as the correct code to the BeepCnt.cpp and BeepCnt.h files. (You'll find the code for this part in the main directory.)

Adding a property is very similar: Right-click the interface and select Add Property…. Then fill in the dialog box as in the following.

The result is that the wizards changed the .idl file by adding the method and property to the interface declaration:

interface IBeepCnt : IDispatch
{
   [id(1), helpstring("method Beep")] HRESULT Beep();
   [propget, id(0), helpstring("property Count")] 
      HRESULT Count([out, retval] long *pVal);
   [propput, id(0), helpstring("property Count")] 
      HRESULT Count([in] long newVal);
};

I modified the IDs of the properties to be "0" so that the property would be considered the default property by Visual Basic. That allowed me to refer to the property by using "Beeper" rather than "Beeper.Count" in my Visual Basic code.

The BeepCnt.h file contains declarations for three new functions, and the BeepCnt.cpp file contains skeleton bodies for the functions:

STDMETHODIMP CBeepCnt::Beep()
{
   // TODO: Add your implementation code here
   return S_OK;
}

STDMETHODIMP CBeepCnt::get_Count(long *pVal)
{
   // TODO: Add your implementation code here
   return S_OK;
}

STDMETHODIMP CBeepCnt::put_Count(long newVal)
{
   // TODO: Add your implementation code here
   return S_OK;
}

To make our component actually do something, regardless of how trivial, we have to write some code. First, we add a counter for the number of beeps to the class and initialize it to one in the constructor:

   long cBeeps;
   CBeepCnt() : cBeeps(1) { }

Next, we write the functions to implement the method and property. Note that the property takes two functions: one to set the property, one to get it. This code is about as easy as it gets:

STDMETHODIMP CBeepCnt::Beep()
{
   for (int i = 0; i < cBeeps; i++)
      MessageBeep((UINT) -1);
   return S_OK;
}

STDMETHODIMP CBeepCnt::get_Count(long *pVal)
{
   *pVal = cBeeps;
   return S_OK;
}

STDMETHODIMP CBeepCnt::put_Count(long newVal)
{
   cBeeps = newVal;
   return S_OK;
}

Creating a client

Visual C++ is Dr. GUI's favorite language, but even the good doctor has to admit that Visual Basic is king for testing objects—so once we added a reference to our object, it was trivial to create a small form

… and to write the code behind it:

Dim BeeperCnt As BeepCnt

Private Sub Beep_Click()
    Text1 = BeeperCnt
    BeeperCnt.Beep
End Sub

Private Sub Set_Click()
    BeeperCnt = Val(Text1)
    Text1 = BeeperCnt
End Sub

Private Sub Form_Load()
    Set BeeperCnt = New BeepCnt
    Text1 = BeeperCnt
End Sub

Give It a Shot!

Dr. GUI knows full well that you won't know whether you really know what we've talked about until you try it—so give it a shot! (Plus—this one's really easy compared to last time.)

Where We've Been; Where We're Going

This time, we've created a simple ATL COM object—with just one property and one method. Next time, we'll explore a more complicated object with multiple interfaces, including interfaces that inherit from other interfaces.

Appendix: Code Samples

Step 0

BeepCntMod.idl

// BeepCntMod.idl : IDL source for BeepCntMod.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (BeepCntMod.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[
   uuid(170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF),
   version(1.0),
   helpstring("BeepCntMod 1.0 Type Library")
]
library BEEPCNTMODLib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");

};

BeepCntMod.def

; BeepCntMod.def : Declares the module parameters.

LIBRARY      "BeepCntMod.DLL"

EXPORTS
   DllCanUnloadNow     @1 PRIVATE
   DllGetClassObject   @2 PRIVATE
   DllRegisterServer   @3 PRIVATE
   DllUnregisterServer   @4 PRIVATE

BeepCntMod.rc

//Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE 9, 1
#pragma code_page(1252)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "1 TYPELIB ""BeepCntMod.tlb""\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

#ifndef _MAC
/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "\0"
            VALUE "FileDescription", "BeepCntMod Module\0"
            VALUE "FileVersion", "1, 0, 0, 1\0"
            VALUE "InternalName", "BeepCntMod\0"
            VALUE "LegalCopyright", "Copyright 1998\0"
            VALUE "OriginalFilename", "BeepCntMod.DLL\0"
            VALUE "ProductName", "BeepCntMod Module\0"
            VALUE "ProductVersion", "1, 0, 0, 1\0"
            VALUE "OLESelfRegister", "\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0409, 0x04B0
    END
END

#endif    // !_MAC

/////////////////////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE DISCARDABLE 
BEGIN
   IDS_PROJNAME               "BeepCntMod"
END

/////////////////////////////////////////////////////////////////////////////


#endif

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
1 TYPELIB "BeepCntMod.tlb"

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by BeepCntMod.rc
//
#define IDS_PROJNAME 100



// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

stdafx.h

// stdafx.h : include file for standard system include files,
//      or project-specific include files that are used frequently,
//      but are changed infrequently.

#if !defined(AFX_STDAFX_H__170BBD90_4DE8_11D2_A2E0_00C04F8EE2AF__INCLUDED_)
#define AFX_STDAFX_H__170BBD90_4DE8_11D2_A2E0_00C04F8EE2AF__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module.
extern CComModule _Module;
#include <atlcom.h>

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__170BBD90_4DE8_11D2_A2E0_00C04F8EE2AF__INCLUDED)

stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes.
//  stdafx.pch will be the pre-compiled header.
//  stdafx.obj will contain the pre-compiled type information.

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>

BeepCntMod.cpp

// BeepCntMod.cpp : Implementation of DLL Exports.


// Note: Proxy/Stub Information
//      To build a separate proxy/stub DLL, 
//      run nmake -f BeepCntModps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "BeepCntMod.h"

#include "BeepCntMod_i.c"


CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_BEEPCNTMODLib);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type.

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
    // registers object, typelib and all interfaces in typelib
    return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}

BeepCntMod.h

/* this ALWAYS GENERATED file contains the definitions for the interfaces */


/* File created by MIDL compiler version 5.01.0164 */
/* at Wed Sep 16 22:08:03 1998
 */
/* Compiler settings for D:\ATL\BeepCntMod\BeepCntMod.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: allocation ref bounds_check enum stub_data 
*/
//@@MIDL_FILE_HEADING(  )


/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 440
#endif

#include "rpc.h"
#include "rpcndr.h"

#ifndef __BeepCntMod_h__
#define __BeepCntMod_h__

#ifdef __cplusplus
extern "C"{
#endif 

/* Forward Declarations */ 

/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * ); 


#ifndef __BEEPCNTMODLib_LIBRARY_DEFINED__
#define __BEEPCNTMODLib_LIBRARY_DEFINED__

/* library BEEPCNTMODLib */
/* [helpstring][version][uuid] */ 


EXTERN_C const IID LIBID_BEEPCNTMODLib;
#endif /* __BEEPCNTMODLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif

BeepCntMod_i.c

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */


/* File created by MIDL compiler version 5.01.0164 */
/* at Wed Sep 16 22:08:03 1998
 */
/* Compiler settings for D:\ATL\BeepCntMod\BeepCntMod.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: allocation ref bounds_check enum stub_data 
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif 


#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID LIBID_BEEPCNTMODLib = {0x170BBD8D,0x4DE8,0x11D2,{0xA2,0xE0,0x00,0xC0,0x4F,0x8E,0xE2,0xAF}};


#ifdef __cplusplus
}
#endif

Step 1

beepcnt.cpp

// BeepCnt.cpp : Implementation of CBeepCnt
#include "stdafx.h"
#include "BeepCntMod.h"
#include "BeepCnt.h"

/////////////////////////////////////////////////////////////////////////////
// CBeepCnt

beepcnt.h

// BeepCnt.h : Declaration of the CBeepCnt

#ifndef __BEEPCNT_H_
#define __BEEPCNT_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CBeepCnt
class ATL_NO_VTABLE CBeepCnt : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CBeepCnt, &CLSID_BeepCnt>,
   public IDispatchImpl<IBeepCnt, &IID_IBeepCnt, &LIBID_BEEPCNTMODLib>
{
public:
   CBeepCnt()
   {
   }

DECLARE_REGISTRY_RESOURCEID(IDR_BEEPCNT)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CBeepCnt)
   COM_INTERFACE_ENTRY(IBeepCnt)
   COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IBeepCnt
public:
};

#endif //__BEEPCNT_H_

BeepCnt.rc

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "1 TYPELIB ""BeepCnt.tlb""\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


#ifndef _MAC
/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "\0"
            VALUE "FileDescription", "BeepCnt Module\0"
            VALUE "FileVersion", "1, 0, 0, 1\0"
            VALUE "InternalName", "BeepCnt\0"
            VALUE "LegalCopyright", "Copyright 1998\0"
            VALUE "OriginalFilename", "BeepCnt.DLL\0"
            VALUE "ProductName", "BeepCnt Module\0"
            VALUE "ProductVersion", "1, 0, 0, 1\0"
            VALUE "OLESelfRegister", "\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END

#endif    // !_MAC


/////////////////////////////////////////////////////////////////////////////
//
// REGISTRY
//

IDR_BEEPCOUNT           REGISTRY DISCARDABLE    "BeepCount.rgs"

/////////////////////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE DISCARDABLE 
BEGIN
    IDS_PROJNAME            "BeepCnt"
END

#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
1 TYPELIB "BeepCnt.tlb"

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

beepcnt.rgs

HKCR
{
   BeepCntMod.BeepCnt.1 = s 'BeepCnt Class'
   {
      CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}'
   }
   BeepCntMod.BeepCnt = s 'BeepCnt Class'
   {
      CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}'
      CurVer = s 'BeepCntMod.BeepCnt.1'
   }
   NoRemove CLSID
   {
      ForceRemove {AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF} = s 'BeepCnt Class'
      {
         ProgID = s 'BeepCntMod.BeepCnt.1'
         VersionIndependentProgID = s 'BeepCntMod.BeepCnt'
         ForceRemove 'Programmable'
         InprocServer32 = s '%MODULE%'
         {
            val ThreadingModel = s 'Apartment'
         }
         'TypeLib' = s '{170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF}'
      }
   }
}

BeepCntMod.cpp

// BeepCntMod.cpp : Implementation of DLL Exports.


// Note: Proxy/Stub Information
//      To build a separate proxy/stub DLL, 
//      run nmake -f BeepCntModps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "BeepCntMod.h"

#include "BeepCntMod_i.c"
#include "BeepCnt.h"


CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_BeepCnt, CBeepCnt)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_BEEPCNTMODLib);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
    // registers object, typelib, and all interfaces in typelib
    return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}

BeepCntMod.idl

// BeepCntMod.idl : IDL source for BeepCntMod.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (BeepCntMod.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";
   [
      object,
      uuid(AE73F2F7-4E95-11D2-A2E1-00C04F8EE2AF),
      dual,
      helpstring("IBeepCnt Interface"),
      pointer_default(unique)
   ]
   interface IBeepCnt : IDispatch
   {
   };

[
   uuid(170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF),
   version(1.0),
   helpstring("BeepCntMod 1.0 Type Library")
]
library BEEPCNTMODLib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");

   [
      uuid(AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF),
      helpstring("BeepCnt Class")
   ]
   coclass BeepCnt
   {
      [default] interface IBeepCnt;
   };
};

#

Final

BeepCntMod.idl

// BeepCntMod.idl : IDL source for BeepCntMod.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (BeepCntMod.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";
   [
      object,
      uuid(AE73F2F7-4E95-11D2-A2E1-00C04F8EE2AF),
      dual,
      helpstring("IBeepCnt Interface"),
      pointer_default(unique)
   ]
   interface IBeepCnt : IDispatch
   {
      [id(1), helpstring("method Beep")] HRESULT Beep();
      [propget, id(0), helpstring("property Count")] HRESULT Count([out, retval] long *pVal);
      [propput, id(0), helpstring("property Count")] HRESULT Count([in] long newVal);
   };

[
   uuid(170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF),
   version(1.0),
   helpstring("BeepCntMod 1.0 Type Library")
]
library BEEPCNTMODLib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");

   [
      uuid(AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF),
      helpstring("BeepCnt Class")
   ]
   coclass BeepCnt
   {
      [default] interface IBeepCnt;
   };
};

BeepCnt.h

// BeepCnt.h : Declaration of the CBeepCnt

#ifndef __BEEPCNT_H_
#define __BEEPCNT_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CBeepCnt
class ATL_NO_VTABLE CBeepCnt : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CBeepCnt, &CLSID_BeepCnt>,
   public IDispatchImpl<IBeepCnt, &IID_IBeepCnt, &LIBID_BEEPCNTMODLib>
{
public:
   long cBeeps;

   CBeepCnt() : cBeeps(1) { }

DECLARE_REGISTRY_RESOURCEID(IDR_BEEPCNT)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CBeepCnt)
   COM_INTERFACE_ENTRY(IBeepCnt)
   COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IBeepCnt
public:
   STDMETHOD(get_Count)(/*[out, retval]*/ long *pVal);
   STDMETHOD(put_Count)(/*[in]*/ long newVal);
   STDMETHOD(Beep)();
};

#endif //__BEEPCNT_H_

BeepCnt.cpp

// BeepCnt.cpp : Implementation of CBeepCnt
#include "stdafx.h"
#include "BeepCntMod.h"
#include "BeepCnt.h"

/////////////////////////////////////////////////////////////////////////////
// CBeepCnt


STDMETHODIMP CBeepCnt::Beep()
{
   for (int i = 0; i < cBeeps; i++)
      MessageBeep((UINT) -1);
   return S_OK;
}

STDMETHODIMP CBeepCnt::get_Count(long *pVal)
{
   *pVal = cBeeps;
   return S_OK;
}

STDMETHODIMP CBeepCnt::put_Count(long newVal)
{
   cBeeps = newVal;
   return S_OK;
}

Part 2: Components with Multiple Interfaces

Where We've Been; Where We're Going

Last time, we created a simple ATL COM object—with just one property and one method. This time, we'll explore a more complicated object with multiple interfaces, including interfaces that inherit from other interfaces. And we'll use that object from a variety of languages. Next time, we'll delve into the mysteries of Automation.

In fact, we wouldn't have to make any changes at all if we kept all the GUIDs the same. Because we're changing the GUIDs of the object, however, we'll have to change the client code that creates the object—but that's about all we'll have to change.

More Interfaces, Please

If you've been following this series on COM, templates, and ATL, you'll recall that in Part 5 we developed an object that had three interfaces: IFoo, IFoo2 (which was derived from IFoo), and IGoo, an independent interface from the other two.

Here's a diagram of our object:

As a reminder, here's the brief version of the IDL, without interface attributes, for the three interfaces:

   interface IFoo : IDispatch
   {
      [id(1)] HRESULT Func1();
      [id(2)] HRESULT Func2([in] int inonly);
   };

   interface IFoo2 : IFoo
   {
      [id(3)] HRESULT Func3([out, retval] int *pout);
   };

   interface IGoo : IUnknown
   {
      HRESULT Gunc();
   };

The reason we derived the IFoo2 interface from IFoo is that we "forgot" an important method in IFoo: we provided a method for setting the internal state of the object, but none for reading that state. By using derivation in this manner, new clients that know about IFoo2 can use the enhanced functionality, but old clients still work—they just don't call Func3. This is the way to add functionality to an existing immutable interface: just derive a new interface from it. Of course, as far as COM knows, these are two entirely separate interfaces.

If you're sharp, you'll have noticed that IFoo is derived from IDispatch rather than from IUnknown. That's because we created it as a dual interface—one that can be called by both early-bound clients using the vtable method we've discussed in the past, and by late-bound clients (such as scripting languages) using Automation calls. We'll discuss the differences between vtable (or custom) interfaces, dispatch (or Automation) interfaces, and dual interfaces in detail in the next column. The id attributes for the methods define the dispatch ID, or dispid, for each method. Each of these IDs must be unique in the interface—that's why the ID for Func3 is 3. (IDs 1 and 2 are used by the base interface.) Note that if IFoo is a dual interface, interfaces derived from IFoo, such as IFoo2, also should be dual interfaces.

IGoo is a plain old custom interface. There's no way for a late-bound client to switch interfaces from the default interface, so there's no need to enable Automation calls by making IGoo a dual interface. (You could give a late-bound client access to a dual IGoo interface if one of the methods in the default interface returned an IGoo interface pointer—but none of the IFoo or IFoo2 methods do.)

Creating the Module, Object, and First Interface

The steps for creating the object are very similar to the steps we took in Part 1 of this ATL series. In fact, creating the module is identical to last time, so you can follow those instructions exactly. Dr. GUI called this module "MultiInterfaceMod"—he'll call the object "MultiInterface."

Next, create a simple object, calling it "MultiInterface." The default interface name is "IMultiInterface." Don't use that—change it to IFoo. The default is to create a dual interface. That's good, since we want to be able to be used by late-binding clients, such as scripting languages.

At this point, you can compare the code with the code for last time just before you added the methods and properties. The code will be just about identical—except for class names and GUIDs.

Next, add the methods for the IFoo interface. Use the Add Method dialog box as described last time, but add the methods described in the preceding IDL (Func1 and Func2) instead of Beep. You don't have to worry about the IDs—the wizard will handle those for you (for this interface, at least). But you will have to get the parameters and attributes for the parameters right.

Steal (uh, reuse) the code from Part 5 of the COM series for the two IFoo methods. Don't forget that you'll need to add and initialize a private member variable for the internal state of the object. Once you've coded the object, build it and test it.

Adding Second and Third Interfaces

Unfortunately, adding additional interfaces to an existing object is harder than you might expect—you have to do a bit of manual labor because there's no wizard to add the interface for you. (It's easy to add another object to your module because there's a wizard to help, however. And there is a wizard to help you implement interfaces for which you have a type library. But because this is a new custom interface, we have no type library to use, so we have to do it by hand.)

On the bright side, the integrated development environment (IDE) does a good job of noticing the changes you make to your source files, including the IDL file, so as soon as you make changes they'll be reflected in the class view window.

Dr. GUI prefers copying and pasting to typing, so for him the first step was to copy the existing (IFoo) interface declaration in the IDL file and paste a copy. (This will be for IFoo2.) Be sure to copy the attributes (in square brackets) just before the interface declaration.

Take a second now to test your understanding of COM and IDL. Can you figure out what will have to be modified to make a declaration for the IFoo2 interface from the following declaration?

   [
      object,
      uuid(09809F2D-7E98-11D2-A320-00C04F8EE2AF),
      dual,
      helpstring("IFoo Interface"),
      pointer_default(unique)
   ]
   interface IFoo : IDispatch
   {
      [id(1), helpstring("method Func1")] HRESULT Func1();
      [id(2), helpstring("method Func2")] HRESULT Func2([in] int inonly);
   };

First, you'll need to change the GUID in the uuid attribute. If you don't have GUIDGen installed on your Tools menu, now is a good time. (Choose Customize from the Tools menu, then the Tools tab on the property sheet, and then add a new menu item that runs guidgen.exe.)

Run GUIDGen to create a new GUID in Registry format. After you copy the new GUID, don't forget to remove the curly braces from the GUID—Registry format is almost the same as IDL format, but not quite. (The other formats GUIDGen generates aren't even close.)

Next change IFoo in the helpstring and the interface name to IFoo2. It's also very important to change the interface's derivation from IDispatch to IFoo—that's the whole point, right? Finally, remove the two method declarations. Since we're inheriting from IFoo, there's no need to redeclare its methods.

When you're done, your IFoo2 declaration should look like the one to follow. (We'll add the method declaration for Func3 later.) I've bolded the places that changed. Note that your GUID won't be the same as mine.

   [
      object,
      uuid(1FF51B0A-79C3-11d2-A31B-00C04F8EE2AF),
      dual,
      helpstring("IFoo2 Interface"),
      pointer_default(unique)
   ]
   interface IFoo2 : IFoo
   {
      // method declarations deleted
   };

As long as we're here, let's create the IGoo interface. We'll make it a custom interface, so in addition to the preceding changes, we'll derive it from IUnknown and remove the dual attribute:

   [
      object,
      uuid(58BD7C84-79C3-11d2-A31B-00C04F8EE2AF),
      // dual,
      helpstring("IGoo Interface"),
      pointer_default(unique)
   ]
   interface IGoo : IUnknown
   {
      // method declarations deleted
   };

You'll also need to edit the coclass declaration to indicate that your object implements the new interfaces. Note that, in addition to adding the interfaces, the good doctor has changed the default interface to IFoo2 so that late-bound clients can access the added function. Here's the coclass section:

   coclass MultiInterface
   {
      interface IFoo;
      [default] interface IFoo2;
      interface IGoo;
   };

If Visual Studio doesn't show the changes in class view right away, just save the IDL file. It should show the three interfaces at the top level, but not under the object.

Inheriting from the New Interfaces

The reason the new interfaces don't show up under the object yet is that we've yet to inherit from the new interfaces. We have to both get the inheritance right and add entries for the new interfaces to the COM map.

Since IFoo2 is derived from IFoo, we'll have IFoo2 but not IFoo in the inheritance list. (IFoo is in the list implicitly, just like IUnknown is.) And since IFoo2 is a dual interface, we use the IDispatchImpl template in the inheritance list, rather than inheriting from it directly.

We inherit from IGoo directly because it's a custom interface.

In other words, we change the class header from:

class ATL_NO_VTABLE CMultiInterface : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CMultiInterface, &CLSID_MultiInterface>,
   public IDispatchImpl<IFoo, &IID_IFoo, &LIBID_MULTIINTERFACEMODLib>
{

to:

class ATL_NO_VTABLE CMultiInterface : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CMultiInterface, &CLSID_MultiInterface>,
   public IDispatchImpl<IFoo2, &IID_IFoo2, &LIBID_MULTIINTERFACEMODLib>,
   public IGoo
{

In contrast, the COM map, used by ATL's implementation of QueryInterface, must contain an entry for each and every interface (except IUnknown). So our COM map changes from:

BEGIN_COM_MAP(CMultiInterface)
   COM_INTERFACE_ENTRY(IFoo)
   COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

to:

BEGIN_COM_MAP(CMultiInterface)
   COM_INTERFACE_ENTRY(IFoo)
   COM_INTERFACE_ENTRY(IFoo2)
   COM_INTERFACE_ENTRY(IGoo)
   COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

Class view will update as soon as we finish the COM map changes.

Adding Methods

Adding methods is the same as before with one gotcha: Visual Studio doesn't pay attention to the fact that IFoo2 is inherited from IFoo, so it will, by default, generate a dispatch ID of 1. You can change this as you declare the method by clicking the Attributes button in the wizard, or you can change the IDL by hand later. If you don't change it, you'll get the following incredibly helpful error message:

midl\oleaut32.dll : error MIDL2020 : error generating type library : 
AddImplType failed : MultiInterface

Nice, huh? As soon as you avoid ID collision by changing Func3's ID to something other than 1 or 2, the error will go away.

Use the IDL code near the beginning of this article as a guide for adding your methods using the Add Method Wizard.

Finally, reuse the code from Part 5 of the COM series for the two new methods. Once you get rid of compiler errors, you should be able to build your COM object.

Changing the Client Code

Part of the beauty of COM is that you can write your objects using any technology you like and old clients will be able to use them. And if we'd kept the ProgID, CLSID, and all the IIDs the same as the non-ATL version, old clients (from the previous articles) would be able to use our new object without even recompiling. It would just work.

However, we did change all of these things, so we'll have to modify our clients to deal with our new object. Since we didn't change the interfaces (only their IIDs), we won't have to change the code that actually calls methods. But we will have to make sure the object gets created properly.

By the way, the good doctor also ported these clients from the previous version to version 6.0 as he made the changes. Except for Visual J++, the porting was entirely automatic. In no case were any source-code changes necessary for porting to the new version!

Visual Basic

The only changes necessary for Visual Basic were to:

With those changes, the code in Part 6 of the COM series worked just fine. And remember—if we hadn't changed the GUIDs and ProgID, we wouldn't have had to change anything at all!

Visual J++

Because Visual J++ version 6.0 is so radically different from Visual J++ 1.1, the good doctor started a new project from scratch rather than attempting to convert the existing project. (If you do convert an old project, you'll have to open it within the Visual J++ environment instead of double-clicking the project file, since Visual J++ no longer uses the DevStudio IDE.)

Once I had a new Windows EXE project (new option in Visual J++ 6.0), I added a class to represent my COM object by selecting Add COM Wrapper from the Project menu. I then created a new class using the source code from the 1.1 project. The only source code changes were to change the import statement to import the right package and to change the name of the class in the new statement.

With those changes, the code in Part 6 of the COM series worked just fine.

C/C++

The C and C++ drivers were also very simple to update. The good doctor had merely to add the proper interface definitions file (MultiInterfaceMod_i.c) to each project so that the definitions of the GUIDs would be available, and include the proper header file. Oh—and the CLSID in CoCreateInstance had to be changed. That was it. (The symbolic name of the IIDs didn't change, although the GUIDs did.)

With those changes, the code in Part 7 of the COM series worked just fine. Again, note that if I had not changed the GUIDs and names in my object, I wouldn't have had to change a thing.

Visual C++ with Smart COM Pointers

Again, the Visual C++ program with smart COM pointers was easy to convert—and I wouldn't have had to convert at all if I'd left the names and GUIDs the same.

The first change was to change the #import statement to import the new type library. (Note that if your type library name is very long, you'll want to rename the namespace created by the #import statement to something shorter to avoid long internal identifiers for templates.)

The other changes were easy: change the using namespace statement to refer to the new namespace, and change the name of the object in the constructors for the smart pointers from:

IFooPtr pIFoo(__uuidof(MyObject)); 

to:

IFooPtr pIFoo(__uuidof(MultiInterface));

That was all it took. With those changes, the code in Part 8 of the COM series worked just fine.

Give It a Shot!

Dr. GUI knows full well that you won't know whether you really know what we've talked about until you try it—so give it a shot! (Plus, this one's really easy compared to last time's…)

Where We've Been; Where We're Going

This time, we explored a more complicated object with multiple interfaces, including interfaces that inherit from other interfaces. Then we used that object from a variety of languages. Next time, we'll delve into the mysteries of Automation.