Chapter 30

Using the Active Template Library

Most Windows programmers will tell you that learning to program with Microsoft's Component Object Model (COM) is a difficult, error-prone process. Until fairly recently, interest in COM was restricted to people who needed to make use of OLE in some form or another; MFC programmers largely had no need to use COM because MFC has provided OLE support classes for several releases. 

COM programming, however, has become increasingly important. The explosive growth of interest in the Internet led Microsoft to restyle OLE controls as ActiveX, and ActiveX depends entirely on the use of COM interfaces. Furthermore, many of the new user-interface and shell features in Exchange, Win95, and Windows NT 4.0 are only available through the use of COM interfaces. 

Introducing the ActiveX Template Library

With the release of Visual C++ 5.0, the ActiveX Template Library (ATL) has matured into a complete ActiveX development solution. The 1.0 and 1.1 releases of ATL introduced ATL as an alternative to MFC for developing ActiveX controls. These early versions of ATL, however, only supported ActiveX COM objects and ActiveX Automation servers. Although these functions were useful in their own right, they didn't satisfy developers' demands for an easier way to write ActiveX controls. 

There's long been an imbalance between OLE control developers and users?whereas the users have had easy-to-use visual tools like Visual Basic and Delphi, the control developers have been stuck writing their controls from scratch with little automated assistance. 

ATL version 2.1, the version that ships with VC++ 5.0, begins to remedy that injustice by supporting ActiveX controls and providing an Object Wizard that can be used to create various types of ActiveX components. 
ON THE WEB
http://www.microsoft.com/visualc/prodinfo/atlinst.htm ATL 2.1 is included with the current version of Visual C++ 5.0. If you're using an older version of VC++, you can still get the latest version of ATL from Microsoft's Web site; if you're using Visual C++ 4.0, 4.1, or 4.2, however, you must also upgrade to Visual C++ 4.2b to use ATL 2.1. 

How ATL Is Implemented

ATL is provided as a set of extensions to the core Visual C++ environment. The first, and most visible, component is the new ?ATL COM AppWizard? item that appears in the Projects tab of the New Project dialog box, as shown in Figure 30.1. As with all of the other AppWizards that you've used throughout this book, the ATL COM AppWizard asks questions specific to ATL projects. We'll go into more detail about the ATL COM Wizard's questions in the section "Creating a New ATL Project." 


The first step in building an ATL project is to tell VC++ to use the ATL COM AppWizard. 

FIG. 30.1 
 
The ATL source code is in DevStudio\VC\ATL; it's well worth a look if you're curious about how it was implemented, because it neatly combines some advanced C++ with familar Windows and COM concepts. 

Templates for COM Objects and Interfaces

The T in ATL stands for "template." C++ templates offer a powerful mechanism for abstracting data into classes. Instead of writing many separate classes for similar types of operations, you can write a template class that can be instantiated to handle many data types. For example, suppose you are writing an e-mail application and want to build classes to handle all of the structures shown in Listing 30.1. 

Listing 30.1 Sample Structures for Template Expansion 

typedef struct 

{ 

int messageClassification; 

LPTSTR senderName; 

LPTSTR subject; 

. . . 

} incomingMessageS; 

typedef struct 

{ 

int messageClassification; 

CFileArray attachedFiles; 

LPTSTR subject; 

Recipient *toRecipients; 

. . . 

} outgoingMessageS; 

typedef struct 

{ 

LPTSTR mailboxPath; 

LPTSTR mailboxName; 

long messageCount; 

. . . 

} mailboxS; 


Without templates, you'd have three separate array classes?even though their implementations would be very similar. You could work around the problem by using an array of void pointers or handles, but then you'd lose the benefits of type safety and argument checking. With templates, you can use the template class like this (this example uses MFC's CList class): 

CList<incomingMessageS, incomingMessageS&> incomingMessageArr; 

CList<outgoingMessageS, outgoingMessageS&> outgoingMessageArr; 

CList<mailboxS, mailboxS&> mailboxArr; 

Here, you're creating three new instances of CList. The template's two arguments are the type of data to store in the array and the type of data that the new array class's member functions should use as arguments, where appropriate. You can then use the template-based classes in your code; the compiler takes care of expanding your template definitions into three new classes, each with a full set of functions to handle your class. 

ATL's more than just templates, though; the L in ATL is for the library code found in \Program Files\DevStudio\VC\ATL\Include. This code serves a purpose similar to MFC: It implements all the complex behavior that ATL supports. The templates wrap the libraries so that they're easy to reuse, but the libraries take care of all the sticky COM- and OLE-oriented details for you. 

ATL Object Wizard

MFC provides the AppWizard and ClassWizard for building application functionality. When you want to add new methods or members to an MFC class, you use the ClassWizard. However, there's no ClassWizard support for the ATL; instead, you use the ATL Object Wizard dialog box, as shown in Figure 30.2, to create new ATL-based classes. 



The ATL Object Wizard dialog box enables you to add new ATL-based objects to your project. 

FIG. 30.2 

In MFC, you use the ClassWizard to add new members or functions. In ATL, you use the right-button context menu on objects in the ClassView tab of the Workspace window. As shown in Figure 30.3, the context menu enables you to add properties and methods to the control's class. 

To add properties or methods to an ATL control, use the right mouse button. 

FIG. 30.3 

Automatic IDL Generation

The Interface Definition Language, or IDL, provides a way for you to tell Windows what interfaces, properties, and classes your controls expose to the outside world, as well as what classes they depend on. IDL has a primarily C-like syntax, but writing IDL from scratch is a fearsomely complex task. One of Microsoft's key objectives in building ATL was to speed the ActiveX control development process by reducing the amount of IDL hacking required to produce ActiveX controls. Note that (as you'll see in the next chapter) you still have to manually edit the IDL file to support control events, but the ATL COM AppWizard provides a usable skeleton from the start. 

What ATL Is

ATL is a lightweight library of templates designed to make it easy to build small, fast ActiveX controls. Of course, if you read Chapter 29, ?Creating ActiveX Controls,? you might be asking why you'd even want to bother with ATL; the MFC ControlWizard does a good job of simplifying the process. 

One good reason: most users will accept software that includes several hundred KB of MFC DLLs if it comes on floppy or CD, but that baggage that large is excessive for controls designed to be downloaded over the Internet. 

Performance is another area where ATL shines; the MFC message-passing architecture imposes overhead that ATL controls avoid. Because ATL is implemented as a set of templates, there's very little runtime overhead for interface queries and passing. 

The ATL Object Wizard hides most of the complexity of adding new properties and methods to your controls. In addition, the Object Wizard supports the full set of ambient and stock properties available to ActiveX controls. 

What ATL Isn't

From the discussion thus far, you might think ATL is the biggest boon to Windows programming since #define STRICT. However, ATL's not intended to be a general-purpose solution for writing any kind of programs; it has some limitations that you need to be aware of. 

Despite what you might be led to think from its close association with MFC, ATL's not an application framework. It doesn't directly support menus, toolbars, document windows, printing, sockets, or most of the other MFC features that you've used in preceding chapters. 

ATL is optimized for use with COM. Although you can accomplish a lot without a deep understanding of COM, real mastery of ATL will require you to spend some time getting your hands dirty with the COM interfaces implemented by ATL. This is of course no different from the learning process involved with MFC?you can get a lot done without knowing anything about the Win32 API, but at some point you have to move beyond the framework and call API routines directly. 
 
If you want to learn more about COM, Kraig Brockschmidt?s Inside OLE is the standard reference work. It?s a thick, imposing book, but Brockschmidt clearly explains how COM works in great detail. 



Furthermore, as you'll see in Chapter 31, there's still a good bit of manual labor involved in customizing ATL-based controls. Microsoft has largely succeeded in reducing the amount of IDL tweaking and hand-customization of COM interfaces needed to make ActiveX controls, but they haven't done away with it entirely. (Good news, though?you can take a working control like the one used in Chapters 31 and 32 and reuse it over and over!) 

When to Use ATL

If you want to build COM interfaces and COM objects, ATL offers some significant advantages. With ATL, you can build fast, lightweight COM servers in a reasonable amount of time. 

ATL is designed entirely to help you implement small, fast COM servers in C++. These servers can expose custom COM or IDispatch-based interfaces; however, ATL doesn't include support for more complex COM-based architectures, such as ActiveX documents. 

ATL is best used for the following types of projects: 
If your project meets any of these criteria and doesn't have a significant user interface, then ATL is probably the best choice for producing the smallest, fastest code. 

On the other hand, if you want to create complex servers that need to support user-interface items, embedded ActiveX controls, or full-blown ActiveX documents, you should use a more robust framework like MFC instead of ATL. 

ATL versus MFC

At first glance, ATL and MFC might seem very different. After all, MFC is a large and complex web of classes built to offer every imaginable application service, from printing to ODBC database access to providing a standard Windows user interface; by contrast, ATL is a small, highly focused set of services targeted at producing COM objects. 

However, there's one critical area of agreement between the two: They were both designed to shift the burden of programming Windows from your shoulders onto their own. In this light, MFC and ATL are partners, not competitors. 

You can see from the preceding sections that MFC and ATL both have their own niches. MFC's broad range of services and excellent integration with the Developer Studio make it easy to produce full-featured applications?but those applications can be bulky. ATL produces small, fast code, but it can't easily be made to do many things that MFC completely automates. 

The good news, though, is that MFC and ATL can productively be used together. The first route is to use MFC classes in your ATL controls. Although this can simplify your programming job, it bogs down your controls with a lot of unneeded baggage. A better approach is to use MFC-based applications to host your controls. By using the techniques described in Chapter 26, "Creating an ActiveX Container Application," you can use MFC where it makes sense while still gaining the benefits of ATL for building controls and extension objects. 

Creating Your First ATL Project

The basic steps needed to create a new ATL project are similar to those needed to create any other kind of project in VC++. Because the ATL Object Wizard is a new addition, though, it's worth looking at its features to understand how, and when, to use them. 

Using the ATL COM AppWizard

As with normal MFC projects, the first step in creating a new ATL project is to choose File, New, click the Projects tab on the New dialog box, and choose "ATL COM Wizard" from the list of projects. Fill in your project's name, and then click OK to start the AppWizard. Refer to Figure 30.1, which shows the AppWizard dialog box. 
 
It's tempting to give the project the same name as the control, but later you'll need to insert the actual ATL controls. To avoid confusing either yourself or the compiler, name the project by appending Control to the control name. 


The ATL COM AppWizard has only one step, as shown in Figure 30.4. The Server Type group has three choices that govern what type of project you'll end up with: 
The ATL COM AppWizard starts your ATL project; you'll still have to insert the actual ATL objects. 

FIG. 30.4 

The Allow merging of proxy/stub files check box controls whether the marshaler for your control is merged into the control's DLL or is kept in its own DLL. Finally, the Support MFC check box controls whether or not the AppWizard will include header files and links to MFC; if you're not using MFC routines in your control, leave it the box unchecked. 

Click Finish when you've set the options appropriately (the defaults of Dynamic Link Library, no MFC support, and no proxy merging is usually correct for most projects.) VC++ displays a confirmation dialog box; when you click its OK button, the AppWizard generates the required C++ class definitions and implementations, plus an IDL file and a proxy/stub makefile, for your project. 

Using the ATL Object Wizard

As soon as you're done with the AppWizard, you'll have a nice, but useless, skeleton. Although the files that the AppWizard generates are necessary, they don't do anything by themselves. You still need to actually insert a new ATL control into your project by choosing the New ATL Object command from the Insert menu. This command invokes the ATL Object Wizard, pictured earlier in Figure 30.2. This Wizard enables you to tell VC++ what type of object you want to create. The left-hand pane lists the three types of ATL objects the Wizard supports: 
After you select the type of object you want to create, you'll see the ATL Object Wizard Properties dialog box, as shown in Figure 30.5. The Names tab enables you to name your new object's C++ class, its COM interface and COM generator (CoClass), and its implementation files. One nice feature of VC++ 5.0 is that you can use long file names when naming files in the Wizards, and this one is no exception. 


The Names tab of the ATL Object Wizard Properties dialog box enables you to specify the class and file names for your new object. 

FIG. 30.5 

Setting the Object's COM Attributes

The Attributes tab, pictured in Figure 30.6, lets you get down to business by specifying the COM properties of your new object. The Threading Model group controls how?and whether or not?your object handles threads. For example: 
The Attributes tab enables you to specify what COM features your object supports. 

FIG. 30.6 
 
Controls for Internet Explorer 3.x should be STA. DOOM controls that might be accessed by several connections at once can benefit from being MTA. In general, the best setting for most uses is Apartment. 


As you learned in Chapter 25, ?An Introduction to ActiveX,? COM objects communicate through interfaces, a collection of function names that describe the possible behavior of a COM object. To use an interface, you get a pointer to it and then call a member function of the interface. All ActiveX Automation servers and controls have an IDispatch interface in addition to their custom COM interfaces. To call a control method of a control, you can use the IDispatch::Invoke() method of the interface (passing in the dispid of the method you want to invoke), or you can look up the COM interface and call it directly. IDispatch was developed so that COM methods could be called from Visual Basic and other languages that don't support pointers; it's now used by Delphi, VBScript, and a host of others. 

A dual-interface control lets you call methods both ways: by using a member function of a custom interface or by using IDispatch. MFC controls and Automation clients only use IDispatch, but this is slower than using a custom interface. Select Dual if you want your object to be usable from Visual Basic or other Automation controllers; select Custom if you know it will only need to talk to other COM objects. 

The third group, Aggregation, governs whether or not another COM class can use this COM class by containing a reference to an instance of it. Choosing Yes means that other COM objects can use this class by including it as part of their own interfaces; No means they cannot, and Only means they must: This object cannot stand alone. 

Finally, the check boxes at the bottom of the Properties dialog box do the following: 

Setting the Object's Miscellaneous Properties

The Miscellaneous tab of the ATL Object Wizard Properties dialog box is shown in Figure 30.7. For most controls, you won't need to change any of these settings, but it's good to know what they're for in case you do need them. 


The Miscellaneous tab enables you to set some lesser-used flags that govern how your ATL control appears and behaves. 

FIG. 30.7 

The View Status group's check boxes are pretty straightforward: Opaque controls whether or not the control's background completely covers what's behind it, and Solid Background specifies whether you want an opaque control to use a solid background color instead of a mixed pattern. Opaque drawing is more efficient because the container doesn't ever have to redraw anything underneath an opaque control. 

The Misc Status group is equally simple. The Invisible at runtime check box governs whether or not your control can appear in a container. Invisible controls can still do useful things?fetch data over the network, fire events, and so on; they don't appear in the container. The Acts like button and Acts like label check boxes enable you to set flags that tell the container how to treat your control. 

The Add control based on combo box enables you to start your control as a subclass of one of the standard Windows controls, enabling you, for example, to build an ATL control that looks like a combo box when it's embedded in a container. 

Finally, the Other group holds three settings that didn't belong anywhere else. Normalize DC tells ATL that you want the device context (DC) used for your control to be normalized each time it's used. Although this ensures consistent drawing, it imposes a small performance penalty. The Insertable check box, when enabled, makes your control appear in the standard Insert Object dialog used in OLE-compatible programs; this makes it possible for users to insert your control in almost any OLE container. The Windowed Only checkbox tells ATL whether or not you want the control to be drawn sans window in containers that support it; as you'll see in Chapter 32, windowless drawing isn't supported in all containers. 

Setting the Object's Stock Properties

Every individual control can have its own custom properties; Windows, however, defines a set of stock properties that are common to all controls. This standardization provides a consistent way for any control to enable users to customize some aspects of its appearance and behavior. 

When creating a new control with the ATL Object Wizard, you can define which stock properties you want to support by specifying them in the Stock Properties tab. As you can see in Figure 30.8, the tab has two lists: one for stock properties supported by the control, and one for unsupported properties. To enable support for a particular property, use the buttons between the two columns to move the desired property into the Supported column. The Object Wizard automatically generates the required code and IDL to use the property. 


The Stock Properties tab enables you to quickly add support for common properties. 

FIG. 30.8 

Generating Code with the Object Wizard

Like the MFC AppWizard and ClassWizard, the ATL Object Wizard generates some code for you. When you insert a new control into your project, the Object Wizard generates a number of files automatically. (In the sections that follow, classname is replaced by whatever you named your class in the Names tab.) 
 
The listings in this chapter are excerpts from the complete source code for the ATLControlWin control. The source is located in the Chap31\ATLControlWin folder on the book's CD-ROM. 

The Control Class Definition

classname.h contains the class definition for your ATL control. The definition makes heavy use of C++ multiple inheritance. A COM class that implements or uses an interface does so by inheriting from a class representing that interface. Because ATL-based classes implement many interfaces, they inherit from many classes. Listing 30.2 shows all the classes that ATLControlWin's main class inherits from. 

Listing 30.2 ATLCONTROLWIN.H?CATLControlWin Inherits from Many Other ATL and COM Classes 

class ATL_NO_VTABLE CATLControlWin :  

public CComObjectRootEx<CComObjectThreadModel>, 

public CComCoClass<CATLControlWin, &CLSID_ATLControlWin>, 

public CComControl<CATLControlWin>, 

public IDispatchImpl<IATLControlWin, &IID_IATLControlWin,  

&LIBID_ATLCONTROLLib>, 

public IProvideClassInfo2Impl<&CLSID_ATLControlWin,  

&DIID__DATLControlWin, &LIBID_ATLCONTROLLib>, 

public IPersistStreamInitImpl<CATLControlWin>, 

public IPersistStorageImpl<CATLControlWin>, 

public IQuickActivateImpl<CATLControlWin>, 

public IOleControlImpl<CATLControlWin>, 

public IOleObjectImpl<CATLControlWin>, 

public IOleInPlaceActiveObjectImpl<CATLControlWin>, 

public IViewObjectExImpl<CATLControlWin>, 

public IOleInPlaceObjectWindowlessImpl<CATLControlWin>, 

public IDataObjectImpl<CATLControlWin>, 

public ISupportErrorInfo, 

public IConnectionPointContainerImpl<CATLControlWin>, 

public ISpecifyPropertyPagesImpl<CATLControlWin>, 

public CProxy_DATLControlWin<CATLControlWin>, 

public IPerPropertyBrowsingImpl<CATLControlWin>, 

public IEnumFORMATETC, 

public IDropSource, 

public IDropTarget 


Now you can see where the T in ATL comes in: All of these classes are template classes. You add support for an interface to a control by adding another entry to this list of interface classes from which it inherits; the compiler takes care of merging the parent class's interfaces with your control. 
 
Notice that the interface names follow the pattern IxxxImpl: This means that the class implements the Ixxx interface. Classes inheriting from IxxxImpl inherit code as well as function names. For example, CATLControlWin inherits from ISupportErrorInfo, not ISupportErrorInfoImpl<CATLControlWin>, even though such a template does exist. That's because the code in the ISupportErrorInfoImpl template implementation class isn't appropriate for an ATL control, so the control inherits from the interface instead of from the implementation. 



There are also four maps in the class header file. The COM map, as shown in Listing 30.3, connects IUnknown::QueryInterface() and all interfaces supported by the control. All COM objects must implement IUnknown, and QueryInterface() is used to determine what other interfaces the control supports and obtain a pointer to them. The macros connect the Ixxx interfaces to the IxxxImpl classes from which CATLControlWin inherits. 

Listing 30.3 ATLCONTROLWIN.H?Using the COM Map to Tie IUnknown to the Control's Interfaces 

BEGIN_COM_MAP(CATLControlWin) 

COM_INTERFACE_ENTRY(IATLControlWin) 

COM_INTERFACE_ENTRY(IDispatch) 

. . . 

COM_INTERFACE_ENTRY(IEnumFORMATETC) 

COM_INTERFACE_ENTRY(IDropSource) 

COM_INTERFACE_ENTRY(IDropTarget) 

END_COM_MAP() 


The property map, as shown in Listing 30.4, links the control with the properties it can support. Because each property will have a unique class ID, this map is necessary so that the OLE subsystem can keep track of the two. In addition, if your control has properties that can be stored and reused, you use the property map to define which properties should persist. 

Listing 30.4 ATLCONTROLWIN.H?Telling VC++ which Properties this Control Supports 

BEGIN_PROPERTY_MAP(CATLControlWin) 

PROP_ENTRY("TextDataPath", dispidTextDataPath, CLSID_ATLControlWinPPG) 

PROP_ENTRY("Alignment", dispidAlignment, CLSID_ATLControlWinPPG) 

PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_ATLControlWinPPG) 

PROP_PAGE(CLSID_CColorPropPage) 

END_PROPERTY_MAP() 


The connection point and message maps, shown in Listing 30.5, work together to let the control interact with the user and with other programs. The IConnectionPoint interface enables your control to send events to other applications, and the connection point map specifies which connection point subclasses your control implements. The message map is heavily used in MFC; as in MFC applications, it enables an ATL control to respond to external events. 


Listing 30.5 ATLCONTROLWIN.H?Activating the Control with the Connection Point and Message Maps 

BEGIN_CONNECTION_POINT_MAP(CATLControlWin) 

CONNECTION_POINT_ENTRY(DIID__DATLControlWin) 

END_CONNECTION_POINT_MAP() 

BEGIN_MSG_MAP(CATLControlWin) 

MESSAGE_HANDLER(WM_PAINT, OnPaint) 

MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode) 

. . . 

MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown); 

MESSAGE_HANDLER(WM_CREATE, OnCreate); 

END_MSG_MAP() 

The Control Class Implementation

The classname.cpp file contains the actual class implementation. The Object Wizard generates a very small file; it originally contains only an OnDraw method that draws "ATL 2.0" in the middle of your control. This file is where you'll do the bulk of your customization because it's where you'll add the member functions that make your control perform. 

The Class Interface Definition

classname.idl holds the Interface Definition Language (IDL) representation of your control's interfaces. IDL is compiled to produce automation type libraries and Registry entries that tell other parts of the system what interfaces your controls support. The Add Method to Interface and Add Property to Interface dialog boxes enable you to largely avoid customizing the provided IDL file; you'll still need to edit it by hand to add support for events, stock properties, or enumerated properties. 

Everything Else

The Object Wizard also generates some other files not listed here; these files include a .def file that defines the control's DLL entry points, a registration script that registers the control in the Registry, an ActiveX Automation type library, and a resource file. With the exception of the resource file (which you edit using the standard DevStudio resource editor), you won't need to modify any of these files. 

In this chapter, you learned how ATL is implemented in C++, what its relative strengths and weaknesses are as compared to MFC, and when it makes sense to use ATL. The next chapter covers the actual process of building a live ActiveX control by using only the ATL. You'll learn how to support property sheets, persistent properties, and a host of other cool features.