Visual C++ 5.0 Simplifies the Process for Developing and Using COM Objects

George Shepherd

George Shepherd develops software and delivers software development seminars around the country. George is co-author with Scot Wingo of MFC Internals (Addison-Wesley, 1996).

By the time you read this, Visual C++® 5.0 should have hit the streets. In it, Microsoft has added a number of new features designed to make it easy to write COM objects. COM objects are rapidly becoming the foundation for Windows®, and many cool Windows features like shell extensions and file viewers—not to mention ActiveX™—are only available through COM. The importance of COM will only increase over time. Unfortunately, as always, the tools for using a new technology lag behind the technology itself.

Visual C++ 5.0 corrects this problem by adding new features that make writing and using COM objects easy. There are new compiler directives and the IDE has been enhanced to provide integrated support for the Active Template Library (ATL). In this article, I'll give an overview of ATL and the new COM support in Visual C++ 5.0.

Quick COM Review

A full discussion of COM is beyond the scope of this article, but I can give you a quick review of the basics, just to jog your memory. At the heart of COM is the notion of an interface. COM makes a formal distinction between interfaces and implementations. In COM, the interface comes first. An interface describes a group of related methods or functions (arguments and return type) an object can implement, without actually implementing them. You can think of an interface as an abstract C++ class—that is, one with no data members and only pure virtual functions. Like many politicians, an interface is all form and no substance. The functions are described, but not implemented. The most basic interface is IUnknown (by convention, interface names begin with I), which all COM objects must implement. IUnknown has three functions: QueryInterface, AddRef and Release.

You could describe a COM interface in C++ using pure virtual functions—and in fact, C++ programs do—but the official language for defining COM interfaces is something called the Interface Definition Language, or IDL. IDL uses a C-like syntax to describe interfaces unambiguously.

COM interfaces are implemented in C++ by COM classes—bodies of code that implement all the functions of at least one interface. If a COM interface is an abstract class, then a COM class is a concrete one in which all the functions for all the interfaces it supports are implemented.

COM classes live within COM servers—DLLs or EXEs that contain the executable code that implements the class. COM classes implemented as DLLs share the same process space with their clients, while those implemented as EXEs live in their own process space.

To create a COM object, you need something called a class object or class factory. A class object is a metaclass for a COM class. That's a fancy way of saying that COM classes can't create themselves; you need a class object to do it. Class objects have an interface called IClassFactory2 (derived from IClassFactory plus three new functions to support licensing) with a function, CreateInstance, to create an instance of the class that the class object represents. Every class has its own class object. If a COM server implements five COM classes, it will have five class objects.

COM classes usually implement more than one interface. Typically this is done in C++ using either multiple inheritance or nesting. With multiple inheritance, the COM class is multiply derived from all the interfaces it implements:

class CQuadrangle : 
      public IQuadrangle {

·

·

·

};

CQuadrangle implements two interfaces: IUnknown and IQuadrangle. The other way of implementing multiple interfaces is to use nested classes:

class CQuadrangle {
  class XIUnknown : public IUnknown {

·

·

·

  }; m_IUnknown;

  class XIQuadrangle : public IQuadrangle {

·

·

·

  }; m_IQuadrangle;
};

With nested classes, each interface is implemented as its own nested class within the main class. Each approach has its advantages and drawbacks, which are beyond the scope of this article. As I'll explain later on, MFC uses nested classes, while ATL uses multiple inheritance.

That's COM in a nutshell, and a very tiny one at that. For a more exhaustive treatment be sure to check out Dale Rogerson's Inside COM (Microsoft Press, 1997) or Kraig Brockschmidt's Inside OLE (Microsoft Press, 1995).

Programming COM

Whether you're implementing a COM object or just using one, writing the code is no small feat, as anyone who has attempted it knows. You have to follow the rules of COM to make sure everything works. For example, to create a COM object, you have to call CoGetClassObject or CoCreateInstance. Then you have to remember to AddRef and Release it correctly, or the reference count by which all COM objects live and die will get out of whack. That could lead to a crash or memory leak—definitely something to avoid. Using COM objects isn't rocket science, but it's a lot more complicated than calling new and delete.

Implementing a COM class is even tougher. You have to type lots of code to do routine things like set up class factories, register your object, implement IUnknown, and so on. There are special functions you have to write, like DllGetClassObject and DllCanUnloadNow for DLLs, and a special WinMain for EXEs. The code is usually identical from one COM object to the next. The part of your object that actually does something unique is often quite small; the rest is just a lot of boilerplate stuff, the kind of thing the framework should do.

Programming COM with MFC

In the effort to make programming COM objects easier, MFC has provided a number of helpful features over the years. For example, checking the Automation box in AppWizard makes it easy to add CCmdTarget-derived classes that support IDispatch to your project. You can also add other interfaces by using the MFC interface macros. There are classes like CCmdTarget, which provide a basic implementation for IUnknown. MFC has DECLARE_OLECREATE and DEFINE_OLECREATE macros that set up class factories for you. MFC also provide standard implementations for DllGetClassObject and WinMain. BEGIN_INTERFACE_PART and END_INTERFACE_PART manage the mechanics of nested classes when you want to build a COM object that supports multiple interfaces. And beyond the base support, MFC implements parts of many useful interfaces such as those required to build OLE Document Objects. This lets you embed your app inside another app, the way you can embed a spreadsheet inside a text document, or vice versa. MFC has over 20,000 lines of code devoted to COM/OLE—code you don't have to write.

But when it comes to small, self-contained, distributable COM objects like ActiveX controls, MFC is deficient in two respects: size and flexibility. MFC is designed primarily for writing applications. Much of the COM support is tied into the document/view architecture in classes like CCmdTarget, CDocument, and CView. You don't want all that code when you're building an ActiveX control, which must be as small as possible so users can download it over the Web in a jiffy. MFC is great for developing full-bodied apps, but frankly it's a bit gargantuan . Because of the way it's designed—as a single monolithic framework meant to be many things to many programmers—MFC is an all-or-nothing proposition. By the time you link CCmdTarget, you've linked practically all of MFC. (On the other hand, if you consider MFC part of the operating system, and can rely on its DLL being present, this is not such a big issue.)

The other problem with MFC is flexibility; it's often hard to tweak MFC's default behavior in little ways. You can only do it if the MFC programmers were thoughtful enough to provide the right virtual functions. And good luck changing the way any of its macros work. The point is, MFC was designed for implementing entire standalone applications, not small, distributable components.

Enter ATL

ATL provides a better way to build small, self-contained, distributable COM objects. Instead of a framework with base classes linked into your program, ATL uses C++ templates to implement the boilerplate code required for COM objects. A C++ template is a function or class that's parameterized by type. For example, instead of writing a class like

class CArray {
   CArray(int size);
   Insert(int x);

·

·

·

};

to implement an array of integers, you could write

template <class T> 
class CArray {
   CArray(int size);
   Insert(T x);

·

·

·

};

to implement an array of any type, not just ints. All you do is add "template <class T>" at the top, and change int to T everywhere you want to parameterize the type. Now you can write:

CArray<int>  x(20);   // array of ints
CArray<long> y(30);   // array of longs

When C++ sees CArray<long>, it replicates all the code in your template, with T replaced with long, as if you typed it in yourself. C++ templates are perfect for collection classes, where you have a lot of code that's similar, if not identical, for different data types. The code for the array class is the same whether it's an array of ints, longs, or floats.

The same is true for COM objects. The code for IUnknown is the same whether the object implements IFileViewer or IQuadrangle or ISomeOtherInterface. ATL uses C++ templates to encapsulate the boilerplate COM code. To get an idea of how it works, take a look at this button class from one of the ATL sample programs that comes with Visual C++ 5.0:

class CAtlButton :
  public CComObjectRoot,
  public CComCoClass<CAtlButton,&CLSID_CAtlButton>,
  public CComControl<CAtlButton>,
  public IDispatchImpl<IAtlButton, &IID_IAtlButton, 
                    &LIBID_ATLBUTNLib>,... // many more
{
  ·
  ·
  ·
};

I don't expect you to understand the details, but you can get the overall picture. Instead of MFC's nested classes, ATL uses multiple inheritance of C++ template classes to implement COM classes. As you can probably guess, CComControl<T> is a template class that implements IOleControl (among other interfaces) for the type T. ATL also provides basic implementations for IUnknown and IDispatch and the boilerplate code necessary for implementing COM aggregation in the most efficient way.

By using templates, ATL helps make COM objects as small as possible. Templates are not linked the way library classes in MFC are. The C++ compiler generates template classes and functions as they're needed at compile time, much the way the preprocessor expands macros. Your final COM object contains only the template classes your program actually uses. Templates also solve the flexibility problem because you can always override any particular template function by writing your own specialization.

The textbook example of this is the min/max templates.

template <class T>
T min(T a, T b) { return a < b ? a : b; }

min computes the minimum of a and b for any type T. For example, min(2, 3) generates min<int> and returns 2, as expected. min(5L, 17L) generates min<long> and returns 5 as a long.

But suppose you wrote min("alpha", "beta"), hoping to compare strings. The way the template is written, the compiler would expand min<char*> to compare pointers, not the strings themselves. To correct this, you can write an explicit specialization for strings.

char* min(char* p1, char* p1)
{ return strcmp(p1, p2) < 0 ? p1 : p2; }

Now when the compiler needs to generate min<char*>, it sees the specialization and uses it instead of generating min<char*> from the template. Using the same technique, you can write specializations for any of the functions in any of the template classes in ATL. Therefore, ATL templates solve the problems of size and flexibility. ATL-based COM objects are smaller because the compiler generates only the code you actually use. Template specialization makes it easy to alter the default template implementations.

Visual C++ Support for ATL

ATL 1.1 has been available for a while now on Microsoft's Web site. Now ATL version 2.1 ships with Visual C++ 5.0. You get all the ATL source and header files, plus a wizard for creating ATL-based COM servers. And Visual C++ supports ATL as a first-class citizen: ClassView now supports editing IDL-based interfaces and ATL-based COM classes.

ATL 1.1 included the templates for creating COM objects and a handy AppWizard for getting started. This AppWizard generated C++ source and header files, an empty IDL file, and a make file for creating COM classes. Writing a COM class in ATL 1.1 involved pumping out an ATL project and writing the IDL code to describe your COM interfaces. The make file was set up so that compiling the IDL file with the Microsoft IDL (MIDL) compiler yielded a header file that contained the appropriate C++ prototypes for implementing your COM class. The older version of the ATL AppWizard was like the old MFC ControlWizard, which made you specify the number of controls and interfaces ahead of time. Once AppWizard generated the server code, you were stuck—you could only change things manually. The new ATL AppWizard is much more flexible. It lets you add new ATL-based objects dynamically.

Microsoft has updated the ClassView to support visual editing of IDL, the ground zero for developing COM interfaces. While C++ is one of the most convenient means for expressing COM classes, it often contains ambiguities that make it difficult to describe interfaces that need to be distributed. IDL is unambiguous.

IDL has a less-competent twin named Object Definition Language (ODL), which is traditionally used to develop type information for Automation. The MFC AppWizard still spits out ODL, but if you want to be cool, you should use IDL because ODL will be gradually phased out.

Creating a COM Server in Visual C++ 5.0

Let's look at what it takes to create a COM server using ATL and Visual C++ 5.0. Starting development of a COM server using the new ATL AppWizard is a lot like creating other projects (such as MFC-based applications or controls). Start by selecting New from the File menu. The resulting dialog is shown in Figure 1.

Figure 1 Invoking the ATL COM AppWizard

When you select ATL COM AppWizard, you get another dialog (see Figure 2) that lets you choose the kind of server you want: DLL, EXE, or Windows NT® service. If you like, you can merge the COM class's proxy and stub code with the server as a convenience. This option places the MIDL-generated proxy and stub code in the same DLL as the server. You can also tell the wizard you'd like to use MFC in your COM object. This option makes sense if you want to use some of the lightweight classes within MFC, such as CString.

Figure 2 ATL COM AppWizard

When you press the Finish button, the ATL COM AppWizard generates several files. For a DLL-based server, you get an IDL file, some resources, and source code to produce a DLL with all the required functions: DllGetClassObject, DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer. The source code generated by AppWizard also includes a global object named _Module derived from CComModule for handling global initializations and other things.

Generating an EXE server yields pretty much the same thing except that, instead of DllGetClassObject and company, you get a WinMain that handles the class object and server lifetime issues specific to EXE servers as well as supporting the command-line options used for COM.

Finally, creating an ATL-based COM service yields a server with code to manage the EXE as a Win32® service. You also get a registry script that registers your COM class with Windows.

New Registration Technology

While I'm on the subject of the registry, the new Registry Script feature in Visual C++ 5.0 is a real boon. When coding COM classes and servers by hand, one of the more arduous tasks is getting the correct registry settings into your project. When developing a COM server from scratch using C++, you have to make several calls to the Win32 registry functions. MFC hides its registry code in COleObjectFactory where it's hard to modify. Modifying the default MFC registry support is possible, but tedious.

ATL includes a new component called the Registrar, which provides data-driven access to the system registry. Instead of calling Win32 API functions to change the registry, you can write a script.

Visual C++ produces a default registry script whenever you create an ATL-based COM class. You'll notice a new resource and a new file (with an RGS extension) for each ATL-based COM class in your server. The registry script creates all the necessary keys for a COM object, including a CLSID key and information about the threading model. ATL's registrar script engine processes the registrar script at runtime, the same way MFC apps load REG files from their InitInstance functions. The difference is that the new registry scripts are more powerful—for example, you can delete keys, which you can't do with REG files.

Adding COM Classes

Once AppWizard generates the basic server project, the next step is to add COM classes. Visual C++ 5.0 includes a new entry in the Insert menu that lets you insert a new ATL object into your project. Selecting New ATL Object from the Insert menu launches a new ATL Object Wizard (see Figure 3).

Figure 3 Inserting an ATL object into your project

Just like adding a new C++ class with Class Wizard, the ATL Object Wizard steps you through the creation of your new COM class. It lets you specify its name, the name of its default interface, and the names of the source files that implement it. You can also specify the threading model, whether to use a dual or custom interface, and whether or not to support aggregation. In addition, you may specify whether or not to use FreeThreadedMarshaler and whether or not you want the COM class to implement ISupportErrorInfo. Another nice thing abou the new ATL Object Wizard is that it is extensible. It uses plain text templates for the objects, which you can modify if you have special requirements.

When you press OK, Visual C++ does several things. It adds C++ source and header files that implement your new class, and it also adds a new interface and a new coclass to your IDL file.

For example, if you add a new ATL object called Quadrangle, Visual C++ generates the IDL code listed in Figure 4. This IDL code describes a COM class named Quadrangle that implements a single interface named IQuadrangle. IDL uses an attribute-thing format. The sections in square brackets specify attributes like interface/class names and keywords for the MIDL compiler to use. The sections in curly braces specify the things to which the attributes apply. Right now the Quadrangle interface doesn't do anything. Once you've got a new COM class added to the server, you'll want to view and edit it. This is where ClassView comes in.

Figure 4 IDL Code for a new ATL Object

[
   object,
   uuid(03527441-7BA5-11D0-8CAA-FD10872CC837),
   dual,
   helpstring("IQuadrangle Interface"),
   pointer_default(unique)
]
interface IQuadrangle : IDispatch  {
};

[
   uuid(0187E5D7-79B1-11D0-8CAA-FD10872CC837),
   helpstring("Quadrangle Class")
]
coclass Quadrangle
{
   [default] interface IQuadrangle;
};

Editing IDL with ClassView

Visual C++ 5.0 enhances ClassView to let you edit ATL-based COM classes as well as the standard C++ classes (see Figure 5). In addition to the normal C++ class icons, you now get interface icons below them, represented by the interface symbol (line and circle) commonly used to illustrate COM interfaces in the COM literature. Double-clicking one of these icons loads the IDL file in the editor window and positions the cursor near the interface definition. At this point, you can embellish the interface as necessary. For example, a Quadrangle object needs properties like length, width, and height.

Figure 5 Editing ATL-based COM classes

Adding Members to an Interface

As with C++ code, you can type directly into the IDL using the editor, or you can use commands to give your object properties and methods. In ClassView, select the interface you want to work on and press the right mouse button to get a context menu that lets you add either properties or methods to your interface. Selecting Property from the context menu displays a dialog box for specifying a property. Using ClassView to add a property to your class adds an entry into the interface definition. Property functions always come in pairs—a get and a put function for each property. IDL uses the keywords propput and propget to identify properties accessed through an interface. For example, if you use ClassView to add the height, width, and depth properties to the IQuadrangle interface, Figure 6 shows what ClassView adds to the definition.

Figure 6 Properties Added to IQuadrangle

interface IQuadrangle : IDispatch
{
   [propget, id(1), helpstring("property Height")] 
      HRESULT Height([out, retval] double *pVal);
   [propput, id(1), helpstring("property Height")] 
      HRESULT Height([in] double newVal);
   [propget, id(2), helpstring("property Width")] 
      HRESULT Width([out, retval] double *pVal);
   [propput, id(2), helpstring("property Width")] 
      HRESULT Width([in] double newVal);
   [propget, id(3), helpstring("property Depth")] 
      HRESULT Depth([out, retval] double *pVal);
   [propput, id(3), helpstring("property Depth")] 
      HRESULT Depth([in] double newVal);
};

In addition to updating the IDL code, ClassView also adds each put and get function to the C++ header and source code files. Adding a method (function) is more or less the same as adding a property. Simply select the interface you'd like to edit, press the right mouse button, and select Method from the context menu. Type the name and signature of the function and ClassView adds it to your class—IDL definition and C++ files.

Some of you C++ fans may be wondering, why do I need IDL? What's wrong with defining my interface using C++ header files? Remember, COM is all about distribution. COM objects can reside anywhere: on your machine, on someone else's machine, over the network, or on the Web. For a COM object in one process space to talk to a COM object in another process space, the COM remoting layer needs to understand the exact size and shape of data involved in the interprocess communication. While you can define COM interfaces using normal C++ pure virtual base classes, using IDL has certain advantages. By defining your COM interface in IDL and compiling with the MIDL compiler, you get source code for a proxy/stub DLL as a by-product. This proxy/stub is used by the remoting layer to marshal method calls between objects and their clients.

Adding New Interfaces to COM Classes

Visual C++ generates a single default interface with any COM class you create using the IDE. For example, using the IDE to create a COM object named CSomeObject adds a default interface named ISomeObject to the COM class. However, most COM classes implement more than one interface. Adding more interfaces is simply a matter of typing the correct IDL code. Remember that IDL always follows the attribute-thing syntax; attributes always precede the things they describe. Notice the IDL code in Figure 4 that specifies the IQuadrangle interface. To add another interface to the IDL, just add a new GUID and create the interface. For example, if you'd like to add another interface for extracting three-dimensional attributes of a shape, you might invent an interface named I3DAspects with functions like Volume and SurfaceArea. Such an interface might have the following IDL definition:

[
   object,
   uuid(87046CC2-79B5-11d0-8CAA-FD10872CC837),
   dual,
   helpstring("I3DAspects Interface"),
   pointer_default(unique)
]
interface I3DAspects : IDispatch
{
   HRESULT Volume([out, retval] double* pdResult);
   HRESULT SurfaceArea([out, retval] double* pdResult);
};

This code merely adds the interface definition. To actually add the interface to the Quadrangle class, you would add the following code to the IDL:

[
   uuid(0187E5D7-79B1-11D0-8CAA-FD10872CC837),
   helpstring("Quadrangle Class")
]
coclass Quadrangle
{
   [default] interface IQuadrangle;
   interface I3DAspects;
}; 

Once you add the interface to the header file and COM class, you have to add the class to the inheritance list in the C++ header file.

class CQuadrangle : 
   public CComObjectRoot,
   public CComCoClass<CQuadrangle, &CLSID_Quadrangle>,
   public IDispatchImpl<IQuadrangle, &IID_IQuadrangle, 
                        &LIBID_GEOSHAPESEXELib>,
   public IDispatchImpl<I3DAspects, &IID_I3DAspects, 
                        &LIBID_GEOSHAPESEXELib>
{
.
.
.
};

Because I3DAspects is a dual interface, ATL requires that the C++ class declaration be added using the IDispatchImpl template.

ATL uses a table-driven mechanism for implementing QueryInterface. Just as MFC maintains an interface map for each of its COM classes, so does ATL, and its macros for the QueryInterface lookup table are similar to those in MFC:

BEGIN_COM_MAP(CQuadrangle)
   COM_INTERFACE_ENTRY2(IDispatch, I3DAspects)
   COM_INTERFACE_ENTRY(IQuadrangle)
   COM_INTERFACE_ENTRY(I3DAspects)
END_COM_MAP()

Finally, you need to declare the new interface functions in your C++ header file for the COM class. The new interface (I3DAspects) is a pure virtual base class, but CQuadrangle is not, so the compiler will choke if you fail to add Volume and SurfaceArea. Once you've added those pieces, you'll have a real COM object and server.

Compiler Support for Using COM Clients

That's a rundown of the new COM server support in Visual C++ 5.0. Now let's take a peek at some of the new support for writing COM clients. Using COM objects isn't quite as tedious as writing them, but does have its own peculiarities. Visual C++ 5.0 introduces a number of enhancements to make it easier. There's a facility to automatically read type information and incorporate it into client applications, new helper classes that hide such details as object creation and the COM reference counting rules, as well as BSTR and VARIANT mechanics. I'll describe each of these in detail.

Type Information and #import

Visual C++ 5.0 integrates type information into the development process. In the past, MFC's COleDispatchDriver was the class to use whenever you wanted to hook up to a COM object that supports Automation. Using COleDispatchDriver is easy. Just create a new class using ClassWizard and ask the ClassWizard to read the type library for you. ClassWizard goes out, reads the type library, and creates all the functions for the dispatch interface described in the type library. In Visual C++ 5.0, this facility has been integrated into a new preprocessor directive: #import. Java™ fans will no doubt recognize #import since Java provided the inspiration for it.

The #import directive reads a type library and turns it into C++ code. The general syntax for #import is:

#import "filename" [attributes]

As with #include, you can also use angle brackets to have the compiler search the PATH, LIB, and any /I compiler option directories. The compiler knows how to read a type library (TLB) file, an executable program, a DLL containing a type library resource, a compound document holding a type library, or any other file format that can be understood by the LoadTypeLib API. Figure 7 provides a summary of the attributes.

Figure 7 Summary of #import Directive Attributes

These attributes may be applied to the #import directive to tailor the TLH and TLI files generated by the #import directive.

exclude

exclude("Name1"[, "Name2",...])

Arguments
Name1: First item to be excluded
Name2: Second item to be excluded (if necessary)

Summary
Excludes specific items from the type library header files generated by the #import directive.

high_method_prefix

high_method_prefix("Prefix")

Arguments
Prefix: Prefix to be used

Summary
By default, member functions expose high-level error-handling properties and methods that are named without a prefix. The high_method_prefix attribute specifies a prefix for naming these high-level properties and methods.

high_property_prefixes

high_property_prefixes("GetPrefix","PutPrefix","PutRefPrefix")

Arguments
GetPrefix: Prefix to be used for the propget methods
PutPrefix: Prefix to be used for the propput methods
PutRefPrefix: Prefix to be used for the propputref methods

Summary
By default, propget, propput, and propputref member functions expose high-level error-handling methods named using prefixes Get, Put, and PutRef, respectively. The high_property_prefixes attribute specifies alternate prefixes for all three property methods.

implementation_only

Summary
The implementation_only attribute suppresses the generation of the TLH header file (the primary header file). When this attribute is specified, the content of the TLI header is in the same namespace as the one normally used in the TLH header. In addition, the member functions are not declared as inline.

The implementation_only attribute is intended for use in conjunction with the no_implementation attribute as a way of keeping the implementations out of the precompiled header (PCH) file.

inject_statement

inject_statement("source_text")

Arguments
source_text: Source text to be inserted into the type-library header file

Summary
Inserts its argument as source text into the type library header, placing the text at the beginning of the namespace declaration that wraps the type-library contents in the header file.

named_guids

Summary
The named_guids attribute tells the compiler to define and initialize GUID variables in old style. That is, using the form LIBID_MyLib, CLSID_MyCoClass, IID_MyInterface, and DIID_MyDispInterface.

no_implementation

Summary
Suppresses generation of the TLI header (containing the implementations of the wrapper member functions). If this attribute is specified, the TLH header won't #include the TLI header file.

no_namespace

Summary
Contents of a type-library header file are normally defined in a namespace. That namespace defaults to the name used in the library statement of the original IDL file. The no_namespace attribute suppresses the namespace in the type library header file.

raw_dispinterfaces

Summary
Tells the compiler to generate low-level wrapper functions for dispinterface methods and properties that call IDispatch::Invoke and return the HRESULT error code. By default, the #import directive generates high-level wrappers, which throw C++ exceptions in case of failure.

raw_interfaces_only

Summary
Suppresses the generation of error-handling wrapper functions and __declspec(property) declarations that use those wrapper functions.

raw_method_prefix

raw_method_prefix("Prefix")

Arguments
Prefix: The prefix to be used

Summary
Member functions expose low-level properties and methods named with a default prefix of raw_ to avoid name collisions with the high-level error-handling member functions. The raw_method_prefix attribute specifies a different prefix.

raw_native_types

Summary
By default, the high-level error-handling methods use the COM support classes _bstr_t and _variant_t in place of the BSTR and VARIANT data types, and raw COM interface pointers to encapsulate the details of handling BSTRs and VARIANTs. The raw_native_types attribute disables the use of these COM support classes in the high-level wrapper functions, and forces the type library header to use low-level data types instead.

raw_property_prefixes

raw_property_prefixes("GetPrefix","PutPrefix","PutRefPrefix")

Arguments
GetPrefix: Prefix to be used for the propget methods
PutPrefix: Prefix to be used for the propput methods
PutRefPrefix: Prefix to be used for the propputref methods

Summary
By default, low-level propget, propput, and propputref methods are exposed by member functions named with prefixes of get_, put_, and putref_, respectively. These prefixes are compatible with the names used in the header files generated by MIDL. The raw_property_prefixes attribute is used to specify alternate prefixes for all three property methods.

rename

rename("OldName","NewName")

Arguments
OldName: Old name in the type library
NewName: Name to be used instead of the old name

Summary
Functions as a search and replace to work around name collision problems. Applying this attribute causes the compiler to replace all occurrences of OldName in a type library with the user-supplied NewName in the resulting header files.

rename_namespace

rename_namespace("NewName")

Arguments
NewName: The new name of the namespace

Summary
The rename_namespace attribute is used to rename the namespace that contains the contents of the type library. The single argument NewName specifies the new name for the namespace.

The #import directive reconstitutes the type library contents in two header files: a primary header file with a TLH (type library header) extension, and a secondary header file with a TLI (type library implementation) extension. The primary header file has the same base name as the type library. It's similar to that produced by the MIDL compiler. That is, the TLH file includes the pure virtual base class definitions for the interfaces. The secondary header file has the same base name as the type library. It contains the implementations for compiler-generated member functions and is included (using #include) in the primary header file.

The #import directive creates seven sections for the primary type library header file: the heading boilerplate, the forward references, the typedefs, the smart pointer declarations, the type information declarations, old-style GUID declarations, and the #include directive for including the secondary header file.

The heading boilerplate includes some comments and other miscellaneous setup information. In addition, this is where COMDEF.H is #included. COMDEF.H defines some macros to help manage some new smart pointers introduced by Microsoft. Also included in this file are some typedefs and forward references for interface declarations. The next section includes the new smart pointers—more on that later. COMDEF.H also includes type declarations and initializes named GUID constants of the form CLSID_CoClass and IID_Interface, similar to those generated by the MIDL compiler. Finally, the primary header file #includes the implementation.

The TLH files generated by #import use a C++ namespace to delineate the components of the type library. The TLH file encloses all sections, except the heading and footer boilerplate sections, within a namespace that has the same name as the original IDL's library statement. Namespaces are just a C++ way of avoiding name collisions. If your library is called GeoShapesExeLib, then you can use the names from the type library header either by an explicit qualification, as in GeoShapesExeLib::Foo, or by adding the line

using namespace GeoShapesExeLib;

immediately after the #import statement in your source file. You can suppress the namespace by using the no_namespace attribute of the #import directive, but doing so may lead to name collisions.

Once you #import the proper type library, your application has immediate access to the COM objects defined in the type library. In other words, using a COM object is as easy as writing

#import <GeoShapes.Exe>

Smart Pointers

One of the coolest things in Visual C++ 5.0 is smart pointers. To understand why, you have to remember that a COM object's lifespan depends on its reference count. A COM object's reference count keeps track of how many clients are using the object. When a client creates a COM object, the object hands an interface pointer back to the client. At that point, the object's reference count is one. The client may decide to call IUnknown::QueryInterface on the original interface to get a new interface. If the interface is available, the object hands the interface back to the client. Now the reference count is two. Whenever a client is done using an object, it calls IUnknown::Release, which decrements the reference count. When the reference count drops to zero, the server is free to destroy the object. Maintaining the proper reference count is vital, and one of the most bug-prone areas of COM programming. This is where smart pointers come to the rescue.

Smart pointers are classes that hide all the reference count bookkeeping by wrapping ordinary C++ pointers. They are "smart" because they know how to create themselves and clean up when they're done. In this case, the constructor calls QueryInterface while the destructor does the Release. In addition, smart pointers behave correctly when copied and overwritten. Scott Meyers devotes a whole chapter to smart pointers in his book, More Effective C++: 35 New Ways to Improve Your Programs and Designs (Addison-Wesley, 1996).

The _com_ptr_t Type

Visual C++ uses the _com_ptr_t template class to encapsulate interface pointers. The _com_ptr_t class wraps a raw interface pointer (IUnknown*) and handles calling a COM interface pointer's AddRef, Release, and QueryInterface functions on behalf of the client. In addition, _com_ptr_t hides the need to call CoCreateInstance when creating a new COM object. There's also a new macro, _COM_SMARTPTR_TYPEDEF, which transforms typedefs of COM interfaces into template specializations of the _com_ptr_t template class.

While Visual C++ provides these new classes and macros, you'll rarely use them in the raw. The _com_ptr_t class is usually hidden in the COM_SMARTPTR_TYPEDEF macro in the TLH files generated by the #import directive. For the IQuadrangle example interface listed previously, #import generates the following line in the TLH file:

COM_SMARTPTR_TYPEDEF(IQuadrangle, 
                     __uuidof(IQuadrangle));

The compiler expands this to:

typedef _com_ptr_t<_com_IIID<IQuadrangle, 
               __uuidof(IQuadrangle)> > IQuadranglePtr;

Now you can use IQuadranglePtr instead of IQuadrangle*. IQuadranglePtr handles the hassles of dealing with reference counting and QueryInterface. AddRef, Release, and QueryInterface are still available if you need them, because _com_ptr_t includes those functions as members.

_bstr_t, _variant_t, and Errors

In addition to _com_ptr_t, Visual C++ has some new functions for working with BSTRs, VARIANTs, and errors. A BSTR is a length-prefixed Unicode string used in IDispatch or dual interfaces. The BSTR data type has several functions associated with it for managing memory and other details, such as SysAllocString and SysFreeString, which allocate and free BSTRs. The _bstr_t class is useful because it manages these details. For example, forgetting to call SysFreeString when you're finished with a string causes a memory leak. The _bstr_t class wraps the BSTR type to provide useful operators (like concatenation) and methods (like finding the length of the string).

COM Automation uses the VARIANT data type as the means for passing parameters back and forth. VARIANTs are self-describing unions with a tag specifying the type of data followed by the arm, which includes the actual data. As with BSTRs, VARIANTs require special handling. For example, Win32 defines several functions: VariantInit for initializing VARIANTs, VariantClear for clearing them out, and VariantChangeType to change one VARIANT type to another. The _variant_t class wraps the VARIANT type to provide useful operators and methods as well as to help manage VARIANT construction and cleanup.

COM Error Support

As good as it is, COM doesn't yet support exceptions. Instead, COM functions must pass back HRESULTs, 32-bit error codes that indicate the success or failure of a function, the category of the error, and the specific error within the category. If you've ever worked with COM, you know that HRESULTs can be tedious. Visual C++ 5.0 includes a new _com_error class to support COM error handling so you don't need to fuss with HRESULTs any longer. _com_error provides lots of useful functionality. For example, it has a member function for retrieving a human-readable string.

Conclusion

That's a quick rundown of the new COM support in Visual C++ 5.0. Obviously, I've glossed over many details. In particular, ATL is worthy of several articles itself. But I hope you can get some idea of how Visual C++ 5.0 makes programming COM easier, and how ATL differs from MFC. The main thing to remember is that MFC is geared more toward medium-to-large applications, while ATL is designed for writing small, self-contained objects like ActiveX controls. If you see ActiveX in your future, I strongly encourage you to check out ATL. Either way—MFC or ATL—new compiler features like #import and smart pointers are sure to make your life easier. Happy programming.

This article is reproduced from Microsoft Systems Journal. Copyright © 1997 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S. and Canada, or (303) 678-0439 in all other countries. For other inquiries, call (415) 905-2200.