Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

The ActiveX Template Library


Dr. Dobb's Journal June 1997: The ActiveX Template Library

Scot is a cofounder of Stingray Software. He can be contacted at [email protected]. George is a senior computer scientist with DevelopMentor and can be contacted at [email protected]. They are the coauthors of MFC Internals (Addison-Wesley, 1996).


Implementing Component Object Model (COM) interfaces and developing COM classes using raw C++ has been the modern day equivalent of developing large applications using assembly language. As with all technologies, COM is improving with age, and the higher-level tools for implementing COM classes are beginning to appear. Microsoft's ActiveX Template Library (ATL) -- included with Visual C++ 5.0 -- is one such tool.

Like MFC, ATL is a framework with lots of interesting things going on inside of it. Unlike MFC (which is useful for creating large applications), ATL exists as a framework for creating COM components. This month we'll examine the current state of software development and see why ATL exists in the first place. In coming months, we'll look further under the ATL hood. ATL is a rich framework with many options, and the only way to truly know which classes to use within ATL is to have a deeper understanding than what comes with the documentation.

Distributing C++

C++ has been a good friend for the past ten years. By giving rise to useful frameworks like MFC, C++ helps simplify Windows development. Frameworks cut down development time because they eliminate the boilerplate code necessary for a program to qualify as a Windows application (remember all the lines of Petzold code necessary to simply get a window up?).

Distributing software these days, however, is becoming as important as reusing code. In this age of component software, it makes sense to package components inside DLLs. While C++ is useful for reusing code, most C++ code falls short when it comes to binary software distribution. Most C++ classes meld class interfaces and implementations. In addition, getting all the great object-oriented functionality requires a number of compiler-dependent features. For example, different compilers mangle class member names differently and may lay out their classes differently. So while you can export entire C++ classes from DLLs, C++ throws a wrench in the works anytime the class layout changes (like when you add a data member) or the client code and object code are implemented using different compilers. Microsoft's COM offers a way out of this problem.

COM

The idea behind COM is to extract the soul of a C++ class into a function table (expressed conveniently in C++ as a pure virtual abstract base class). To that end, COM is really a series of rules (set forth by Microsoft) formalizing the distinction between interface and implementations. In addition to this key distinction, the folks from Redmond threw in several other goodies including a way to arbitrarily widen the connection to an object at run time (using IUnknown::QueryInterface()) and operating-system support to locate implementations and remote interfaces.

COM and C++

When encountering COM for the first time, many programmers are taken aback by the intent of COM. Whereas C++ is about code reuse (that is, you inherit functionality from base classes, tweaking the behavior to suit your needs), COM is all about software distribution and reuse at the binary level. To that end, COM is more of a description of how to write software than how to derive your software from existing implementations.

Getting an object to be COM-compliant requires boilerplate code. Particularly, every COM class has to implement IUnknown and map the three IUnknown functions (QueryInterface, AddRef, and Release) at the top of every COM interface to their implementations. In addition, polite COM classes support COM aggregation as well as Automation. Finally, COM objects need to live in servers (DLLs or EXEs) and require accompanying Class Objects (or class factories). The code necessary to implement these features is boilerplate, and it makes sense to devise some sort of framework for implementing all that boilerplate code. The ATL is such a framework.

ActiveX, OLE, and COM

COM is simply the plumbing for a series of higher-level application integration technologies consisting of such items as ActiveX Controls and OLE Documents. These technologies define protocols based on COM interfaces. For example, for a COM object to qualify as a minimum OLE Document object, that COM Object has to implement at least three interfaces, IPersistStorage, IOleObject, and IDataObject. You may choose to implement the higher-level features like OLE Documents and controls (see Kraig Brockschmidt's Inside OLE, Microsoft Press, 1995, about how to do this in raw C++). However, it makes more sense to let some sort of application framework do the grunt work. Of course, that's why there's the MFC.

ActiveX, MFC, and COM

While the pure plumbing of COM is interesting in itself, it's the higher-level features that sell applications. MFC is a huge framework geared toward creating entire Windows applications. Inside MFC you'll find utility classes, a data management/rendering mechanism (the document/view architecture), and support for OLE Documents, drag-and-drop, Automation, and ActiveX Controls. You probably don't want to develop an OLE Document application from scratch. You should go to MFC whenever you need that. However, if you ever need to create a small or medium-size COM-based service, you may want to turn away from MFC so you don't have to include the baggage MFC maintains for the high-level features.

So while you may use raw C++ to create COM components, doing so forces you to spend a good portion of the time hacking out the boilerplate code (things like IUnknown and class objects). Using MFC to write COM-based applications turns out to be a less painful way of adding big-ticket items to your application, but it's difficult to write lightweight COM classes in MFC. ATL sits between pure C++ and MFC as a way to implement COM-based components without having to type in the boilerplate code and without having to buy into all of MFC's architecture. ATL is basically a set of C++ templates and some other support for writing COM classes.

C++ Templates

The key to understanding the ActiveX Template Library is understanding C++ templates. Despite the scary template syntax (well, any C++ syntax is scary when first encountered), the concept of templates is fairly straightforward. C++ templates are sometimes called "compiler-approved macros" which is an appropriate description. Think about what macros do for a minute. When the preprocessor encounters a macro, the preprocessor looks at the macro and expands the macro into some regular C++ code. The problem with macros is that they are sometimes error prone and are not typesafe. If you use a macro and pass an incorrect parameter, the compiler won't complain, but your program may crash. Templates are like typesafe macros. When the compiler encounters a template, the compiler expands the template. But because templates are typesafe, the compiler catches any type problems before the user does.

The canonical example of using a template is a dynamic array. Imagine you need an array for holding integers. Rather than declaring the array with a fixed size, you want the array to grow as necessary. So you develop the array as a C++ class. Then someone you work with hears about your new class and wants the exact same functionality. However, the other person wants to use floating-point numbers in the array. Rather than pumping out the exact same code (save for using a different type of data), you can use a C++ template. As you can imagine, this is useful for implementing boilerplate COM code, and templates is the mechanism ATL uses for providing COM support.

ATL Roadmap

The road to code reuse using templates is different than what you're used to with conventional C++ development. Rather than reusing code by inheriting functionality from base classes, components written using templates reuse code by template substitution. All the boilerplate code from templates is literally pasted into the project. If you look at the ATL source code, you'll find ATL consists of a collection of header and C++ source-code files -- most of it residing inside ATL's include directory. Here's a rundown of the ATL files inside each of them:

  • ATLBASE.H contains ATL's function typedefs, various structure and macro definitions, some smart pointers for managing COM interface pointers, thread synchronization support classes, definitions for CComBSTR, CComVariant, and threading and apartment support.
  • ATLCOM.H contains template classes for class object/class factory support, various IUnknown implementations, support for tear-off interfaces, type information management and support. ATL's IDispatch implementation, COM enumerator templates, and connection point support.
  • ATLCONV.CPP and ATLCONV.H include support for Unicode conversions.
  • ATLCTL.CPP and ATLCTL.H include the source code for ATL's IDispatch client support and event-firing support, CComControlBase, the OLE embedding protocol support for controls, and property-page support.
  • ATLIFACE.IDL and ATLIFACE.H include an ATL-specific interface named IRegistrar.
  • ATLIMPL.CPP implements such classes as CComBSTR, which were declared in ATLBASE.H.
  • ATLWIN.CPP and ATLWIN.H provide windowing and user-interface support, including a message mapping mechanism, windowing class, and dialog support.
  • STATREG.CPP and STATREG.H include code for implementing a COM component (the "registrar"), which makes appropriate entries into the registry.

Using ATL

ATL includes a COM Wizard for generating a skeleton EXE, DLL, or Windows service. This is handy because it's tedious to set up the right kind of server for it to live in. The ATL COM Wizard takes care of those details. Using the ATL COM Wizard to write a lightweight COM class (we'll look at Controls in a future column) yields several products.

First you get a project file for compiling your object. The project file ties together all the source code for the project and maintains the proper build instructions for each of the files. Secondly, you get boilerplate Interface Definition Language (IDL) code. This is important because IDL is the primary file when writing COM code objects -- it's the starting point for real COM development.

IDL is a purely declarative language for describing COM interfaces. Once a COM interface is described in IDL, a pass though the Microsoft Interface Definition Language compiler (the MIDL compiler) produces the pure abstract base classes you need to write your COM classes, a type library, and the proxy stub DLL necessary for standard COM remoting.

Once the ATL COM Wizard pumps out a skeleton server for you, you need to put some COM classes inside it. ATL includes an Object Wizard (see Figure 1) for just such a task.

Using the Object Wizard to generate a new object adds C++ source and header files to your project. In addition, the Object Wizard adds an interface to the IDL code. The best part about using the Object Wizard is that you don't need to remember all the weird IDL syntax.

ATL and COM Classes

The ATL approach to implementing COM classes is somewhat different than either the raw C++ approach or the MFC approach. Developing COM classes using raw C++ involves multiply inheriting a single COM class from at least one COM interface, then writing the code for the C++ class. You've got to add any extra features by hand (like supporting COM aggregation).

Contrast this to the MFC approach to COM classes, which involves macros that define nested classes (with one nested class implementing each interface). The ATL approach involves inheriting a C++ class from several template-based classes. For example, if you ask the Object Wizard to create a COM class named AtlOb, Example 1 is the code emitted by Object Wizard.

While ATL includes quite a few COM-oriented C++ classes, those in Example 1 are enough to get a flavor of how ATL works. The most generic ATL-based COM objects derive from these base classes.

CComObjectRoot implements IUnknown and manages the identity of the class. This means that CComObjectRoot implements AddRef() and Release() and hooks into ATL's QueryInterface mechanism. CComCoClass manages the COM class's class object and some general error reporting. In the aforementioned case, CComCoClass adds the class object that knows how to create CAtlOb objects. Finally, the code produced by the ATL Objects Wizard includes an implementation of IDispatch based on the type library produced by compiling the IDL. The default IDispatch is based on a dual interface -- an IDispatch interface followed by the functions defined in the IDL.

Implementing COM classes using ATL is different from implementing them using pure C++. The Tao of ATL differs to what you may be used to when developing normal C++ classes. With ATL, the most important part of the project is the interfaces, which are described in IDL. By adding functions to the interfaces in the IDL code, you automatically add functions to the concrete classes implementing the interfaces. This is because the projects are set up such that compiling the IDL file yields a C++ header file with those functions. All that's left for you to do after adding the functions in the interface is to implement those functions in the C++ class. The IDL file also provides a type library so the COM class can implement IDispatch. However, while ATL is useful for implementing light-weight COM services and objects, ATL is also a new means by which you can create ActiveX Controls (as we'll cover in future columns).

Conclusion

The Component Object Model solves many problems of software distribution and is here to stay. While we've always been able to make single entry point calls into DLLs and make single RPC calls, COM permits gathering functions together into cohesive units. COM also provides a versioning mechanism -- a way to introduce new capabilities into a system without breaking existing software.

Riding shotgun over COM is a series of technologies called "ActiveX," which consists of various Internet technologies, controls, and document-management facilities. Developing applications that use this functionality is often a matter of supplying the boilerplate code necessary to fulfill the protocols defined by the technologies. Frameworks exist for this reason.

However, there's another class of components (custom services) that don't require the tons of plumbing required by the main ActiveX technologies. Instead of using C++ to develop these components from scratch, the ATL provides the pure COM plumbing so you can concentrate on the services provided by the component.

ATL is a lightweight framework for creating small COM objects (as opposed to MFC, which is a framework for developing stand-alone applications) and is puny compared to MFC. However, ATL still has a lot going on inside. ATL takes care of such details as class object and server lifetime issues, COM identity (implementing IUnknown and COM aggregation), and implementing IDispatch. In coming months, we'll probe deeper into ATL to see how it handles issues such as COM identity, how ATL deals with COM server lifetime issues, how it implements IDispatch, and how ATL implements ActiveX Controls.

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.