Полезная информация

Chapter 13
Creating ActiveX COM Objects and Custom Interfaces Using ATL

Creating ActiveX COM Objects and Custom Interfaces Using ATL

In the past, designing COM objects was relegated to the use of large application frameworks such as MFC or to building your own components. Using large application frameworks eases the construction and implementation of COM objects but requires a significant amount of overhead code for the building and distribution of the COM servers. Conversely, building your own COM server framework results in fast, lightweight objects but requires a significant amount of up-front programming to implement the control.

To address this dilemma, Microsoft created the ActiveX Template Library (ATL), which provides a middle ground between large application frameworks and building your own COM objects. ATL is a set of template-based C++ classes that simplifies the programming of COM objects. ATL provides the necessary COM foundation, allowing the focus to be on programming the functionality of your objects. ATL is shipped with Visual C++ 5.0 and is backward compatible with Visual C++ 4.1 and 4.2. ATL can be downloaded separately from the Microsoft Web site at http://www.microsoft.com.

NOTE: Users of Visual C++ 4.0 must upgrade to version 4.1 or higher of the Visual C++ compiler in order to use the ActiveX Template Library.

The goal of ATL is to allow for the easy creation of small, fast COM servers. This goal has been achieved by the following:

Static library and DLL dependencies are removed by providing all of the source code for the ATL libraries. The source code for ATL is a set of C++ class templates. The small set of ATL code gets compiled into the COM server during the building process. The overhead of ATL in an in-process COM server is less than 5K.

Reaping Benefits of the ActiveX Template Library

The ActiveX Template Library is the first attempt to create a C++ framework with the sole intention of creating COM objects. Since COM object creation was the primary goal of the framework, ATL has been trimmed of unnecessary baggage such as bloated UI components. The use of ATL provides developers with a number of benefits including the following:

Support for Multiple Server Types

One of the biggest benefits of using ATL is the support for the creation of multiple COM server types. When using the ATL wizard, the shell classes for each server type are automatically created during project initialization. ATL provides support for the following types of COM servers.

Threading Models Supported by ATL

ActiveX has different threading models that can be utilized by COM servers. The ATL library provides built-in support for these different types of threading. Each model provides different capabilities, and care must be taken when deciding which model will be supported by the COM server.

When implementing free-threading servers, the burden for protecting data within a COM class from simultaneous updates or access by multiple threads falls on the programmer. Multiple threads may be attempting to access local data within the same instance of a COM object. ATL does not provide built-in data access synchronization. The use of Win32 synchronization objects such as events, semaphores, mutexes, and critical sections is needed for protecting COM class data.

Tear-Off Interfaces

A tear-off interface, a new concept introduced in the ATL framework, is an optimization of a regular COM interface in that it doesn't actually exist until it is instantiated by a call to QueryInterface on your object for that interface. Since the interface does not exist until asked for, it does not consume system memory resources.

When the Release method is called on the interface and the reference count on that interface returns to zero, the interface is removed from memory. Typically, tear-off interfaces are used only for those interfaces that are expected to be used less often than others, such as ISupportErrorInfo.

NOTE: Tear-off interfaces should not be used for commonly used interfaces because the overhead of memory allocation and deallocation and memory fragmentation would outweigh the benefits of the interface.

To implement tear-off interfaces, declare a class that inherits from all the interfaces you want to implement in the tear-off, as well as from CComTearOffObjectBase<class Owner>, where Owner is the class of the main object. Then provide a normal BEGIN_COM_MAP...END_COM_MAP() specification of interfaces in the tear-off, and use the COM_INTERFACE_ENTRY_TEAR_OFF macro in the main object's COM map.

Implementing Interface Aggregation

You can implement aggregation in ATL servers with very little work. Aggregation is when an object exposes another object's interface pointer as its own. For example, if an application has a pointer to interface A and needs to access interface B, and if interface A supports aggregation, the application can call QueryInterface on interface A to obtain interface B. The only penalty imposed for supporting aggregation is needing a somewhat larger server. The benefit is the flexibility to expose interfaces from objects contained in the server.

In order to make a server aggregatable, use the macro DECLARE_AGGREGATABLE in the COM object's class. If aggregration is not desired, use the macro DECLARE_NOT_AGGREGATABLE to disable aggregration. By default, aggregration is supported by projects created with the ATL COM AppWizard.

Built-In Support for Error Handling

ATL supports the OLE error reporting mechanism with the Error() member function in the CComCoClass and CComISupportErrorInfoImpl classes. These classes each have a member, InterfaceSupportsErrorInfo(), that indicates whether returning rich error information is supported. By using this mechanism, custom COM interfaces can provide helpful information to the end user if error situations are encountered.

Creating a COM Server Using ATL

When using the ActiveX Template Library, the creation of COM servers is a trivial task. The ATL installation creates an ATL COM AppWizard that can be accessed from the Visual C++ development environment. The ATL COM AppWizard, like the MFC AppWizard, presents the user with a step-by-step set of options for the creation of a COM server. The end result of running the wizard is a ready-to-be-built project with all necessary class template source code for the COM classes and interfaces that will be implemented within the project.

NOTE: In Chapter 12, an interface library entitled IFish was created. The IFish and IBass interfaces built in that example will be constructed using the ATL library. The project AtlCustomBass will create a COM class CAtlCustomBass, used for accessing the IFish interfaces. The CAtlCustomBass class will be implemented as an in-process server.

Using the ATL COM Wizard to Create a COM Server

To get started using the ATL COM AppWizard, a new project must be created. A new project can be created by performing the following steps:

  1. Select the New command from the File menu in the Visual C++ development environment.

  2. From the New dialog, select the Projects tab.

  3. From the Projects tab, select the ATL COM AppWizard (see fig. 13.1).

    FIG. 13.1
    Select the ActiveX Template Library COM AppWizard to create an ATL-based COM server.

  4. Enter the name of the project in the Name edit box. For this project, enter the name AtlCustomBass. Then click the OK button.

    The ATL COM AppWizard is presented in Figure 13.2.

    FIG. 13.2
    Choose the COM object options by using the ATL COM AppWizard.

  5. Select the type of COM server to create. The AtlCustomBass project is created as a DLL. It is good practice to select the option "Allow merging of _proxy/stub code" (AtlCustomBass selects this option). The stub code option provides parameter marshalling for the objects. If your server needs to support MFC, select the option Support MFC. Click the Finish button to create the ATL COM server. A New Project Information dialog is displayed (see fig. 13.3). Click OK, and the project is automatically created.

FIG. 13.3
The New Project Information dialog box recaps the ATL COM server options.

After creating the project, a COM object needs to be added to the project. To add a COM object, perform the following steps:

  1. Select the New Class command from the Insert menu of the Visual C++ development environment. The New Class dialog is displayed, as shown in Figure 13.4.

    FIG. 13.4
    Use the New Class dialog box to create COM classes.

  2. Select ATL Class from the Class type drop-down list. Selecting ATL Class means that the new class will be derived from the ATL framework.

  3. Type AtlCustomBass1 in the Name edit box.

  4. Select the Custom Interface type radio button. There are two interface type choices:

  5. Specify 2 (two) in the Number of interfaces edit box. This setting determines how many interfaces the COM object will contain. The New Class dialog imposes a limit of 3. Again, this limit is imposed by the dialog, not the ATL library. The AtlCustomBass project will contain two interfaces (IFish and IBass) in the object.

  6. Select the Edit button to change the default interface names. The Edit Interface Information dialog shown in Figure 13.5 is displayed.

    FIG. 13.5
    The interface names can be changed from the Edit Interface Information dialog.

  7. To change the names of the interfaces, click each interface shown in the Interface Names list. The CAtlCustonBass1 class supports the IFish and IBass interfaces. Enter IFish and IBass in the Interface Names list. Click the OK button when finished.

  8. Click the OK button in the New Class dialog box to create the class.

All the templates for the ATL COM server are now created. What remains is for the server's specific implementations to be incorporated. Before performing the customizations, you need to examine the results created by the ATL COM AppWizard.

Examining the Results of the ATL COM Wizard

A total of 11 files were created when the ATL COM AppWizard and New Class dialogs were used to create the ATLCustomBass project. Table 13.1 shows the filenames and purpose of each file created.
Table 13.1 Files Created for the AtlCustomBass Project
Filename Purpose
Stdafx.cpp Contains the includes needed globally for the project. This usually generates precompiled headings used by all other c or cpp files.
AtlCustomBass.cpp Contains the COM server entry point implementations and COM class registration function.
AtlBass1.cpp Skeleton cpp file for the AtlCustomBass COM object. All class and interface implementations are placed in this file.
AtlCustomBass.def Export definition file for the COM object server.
AtlCustomBassps.def Export definition file for the interface library. The interface library is used for generating proxy code for parameter marshaling.
AtlBass1.h AtlCustomBass COM object definition file.
AtlCustomBass.idl AtlCustomBass COM class and interface IDL definitions.
Resource.h Standard resource file used for icons, version information, and so on.
Stdafx.h Wrapper include file that includes all needed ATL header files.
Atlcustombassps.mk Makefile used for compiling the interface definition file. This file produces the parameter marshaling code for the COM interfaces.

As you can see, the AppWizard and New Class dialog have been busy on your behalf creating the templates necessary for creating COM objects. You now need to implement the actual interfaces.

Implementing the COM Server Access Functions

Chapter 12 includes information about several access functions that must be exported from the COM server. These functions are accessed by the COM libraries to load, unload, and register the objects within the server. These functions are shown in the definition file for the CAtlCustomBass server (see Listing 13.1).

Listing 13.1 AtlCustomBass.def--Library Definition File for CAtlCustomBass

; AltCustomBass.def : Declares the module parameters.
DllCanUnloadNow @1 PRIVATE
DllGetClassObject @2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE

The ATL library provides all of the necessary code for implementing the DLL access functions. For the CAtlCustomBass project, these functions are implemented in the file (see Listing 13.2).

Listing 13.2 AtlCustomBass.cpp--ATL Implementation of DLL Access Functions for COM Server

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "AltCustomBass.h"
#include "AltCustomBass1.h"
#include "dlldatax.h"
#include "AltCustomBass_i.c"
extern "C" HINSTANCE hProxyDll;
CComModule _Module;
OBJECT_ENTRY(CLSID_CAltCustomBass1, CAltCustomBass1)
// DLL Entry Point
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
if (dwReason == DLL_PROCESS_ATTACH)
_Module.Init(ObjectMap, hInstance);
else if (dwReason == DLL_PROCESS_DETACH)
return TRUE; // ok
// Used to determine whether the DLL can be unloaded by OLE
STDAPI DllCanUnloadNow(void)
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
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)
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
return _Module.GetClassObject(rclsid, riid, ppv);
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void)
return S_OK;

Using IDL to Create Object Definitions

All COM classes and interfaces are defined through IDL, the Interface Definition Language. IDL is also used in the creation of RPC interfaces and Automation interfaces. When the AtlCustomBass project was created, the COM AppWizard generated the file AtlCustomBass.idl. This file contains template structures for the COM class and interfaces specified when running the AppWizard. All methods for the custom interfaces must be added to this file. Listing 13.3 illustrates the contents of AtlCustomBass.idl with the methods for custom interface IFish and IBass.

Listing 13.3 AtlCustomBass.idl--Adding Custom Interface Methods to the Project Interface Definition File

import "unknwn.idl";
#define MAX_FISH_BSTR_LEN 255
// This file will be processed by the MIDL tool to
// produce the type library (AltCustomBass.tlb) and marshalling code.
helpstring("IFish Interface"),
interface IFish : IUnknown
import "oaidl.idl";
HRESULT IsFreshwater([out] BOOL *pBool);
HRESULT GetFishName([out, string] FISH_BSTR p);
helpstring("IFish Interface"),
interface IBass : IUnknown
import "oaidl.idl";
HRESULT GetLocation([out, string] FISH_BSTR p);
HRESULT SetLocation([in, string] FISH_BSTR p);
HRESULT EatsOtherFish([out] BOOL *pBool);
}; [
helpstring("AltCustomBass 1.0 Type Library")
helpstring("AltCustomBass1 Class")
coclass CAltCustomBass1
[default] interface IFish;
interface IBass;
}; };

In the IDL file for AtlCustomBass are definitions for two interfaces, IFish and IBass, and the COM class, CAltBass1. As you can see from Listing 13.3, both the IFish and IBass interfaces are derived from the IUnknown interface. In this example, a new type has been defined, and that type is FISH_BSTR. FISH_BSTR is defined as a wide-character string that is 255 characters in length. The wide-character string is used so that the server can be compiled as either multibyte (default) or UNICODE. The maximum length must be specified in the IDL definition. This is a requirement of the MIDL compiler used to compile the IDL file. In order for the MIDL compiler to produce code that handles parameter marshaling, the absolute length of the data item passed into functions must be known.

The COM class CAltCustomBass1 is a COM Object library that acts as a container for the COM interfaces IFish and IBass. The specified coclass identifies the definition as a COM class. As you can see from the definition, the IFish interface is the default interface. This is the pointer that is returned when the IID_IUnknown interface is queried.

The IDL file is compiled separately from the AtlCustomBass project. When the AtlCustomBass project is built, the AtlCustomBass.idl file is compiled with the MIDL compiler first. This step is automatically done as part of the build process.

The IDL can also be compiled separately outside the IDE. The ATL COM AppWizard generates a separate makefile for the IDL file (AtlCustomBassps.mk). The makefile program nmake can be run from a command prompt to compile the IDL file. The command line would look like this:

nmake -fatlcustombassps.mk

Implementing the COM Interface

Implementing the COM interface using the ATL library is a simple process. For the programmer familiar with C++, this implementation is as simple as adding the interface methods to the COM class. The ATL library takes care of handling all of the IUnknown and IClassFactory interfaces, thus removing this burden from the developer. Even the MFC framework requires that the IUnknown interface must be handled by the programmer.

Listing 13.4 shows the COM class CAtlCustomBass definition file (AtlCustomBass.h). The only modifications needed for the AtlCustomBass COM server is to add the public interface methods and the variables needed by the class.

Listing 13.4 AtlCustomBass.h--C++ Class Definition for the AtlCustomBass COM Server

class CAltCustomBass1 :
public IFish,
public IBass,
public CComObjectRoot,
public CComCoClass<CAltCustomBass1,&CLSID_CAltCustomBass1>
// Remove the comment from the line above if you don't want your object to
// support aggregation. The default is to support it
DECLARE_REGISTRY(CAltCustomBass1, _T("AltCustomBass.AltCustomBass1.1"), _T("AltCustomBass.AltCustomBass1"), IDS_ALTCUSTOMBASS1_DESC, THREADFLAGS_BOTH)
// IFish
STDMETHOD(IsFreshwater)(BOOL* pBool);
// IBass
STDMETHOD(EatsOtherFish)(BOOL *pBool);
WCHAR m_FishName[255];
BOOL m_bFreshWater;
WCHAR m_Location[255];
BOOL m_bEatsFish; };

To support the IFish and IBass interfaces, the custom methods for each interface have been defined as standard C++ methods.

The ATL Template Library was developed to directly support and work with COM. This is in contrast to the MFC framework, which does not directly use COM but has hooks to manipulate COM. The COM class CAltCustomBass is derived from the classes and interfaces shown in Table 13.2.
Table 13.2 Base Classes and Interfaces for CAtlCustomBass1
Class Description
IFish IFish COM interface, which is one of the custom interfaces supported by the class
IBass Another custom interface specified during class construction
CComObjectRoot ATL class that implements all reference counting and thread model-specific implementations
CComCoClass ATL class that implements the class factory for the object, aggregation, and error handling

The ATL library supports multiple interfaces through inheritance rather than through nested classes. MFC uses nested classes for the support of multiple interfaces.

For a more thorough discussion of techniques used for supporting multiple interfaces, refer to Chapter 14, which demonstrates the difference between direct inheritance and nested classes.

Implementing the C++ method that will support the COM interface is a straightforward matter. Listing 13.5 shows the implementation of the IFish and IBass COM interfaces used in the CAltCustomBass1 class.

Listing 13.5 CAltCustomBass1.cpp--Listing of CAltCustomBass1.cpp, Which Implements the COM Interfaces for IFish and IBass

wcscpy( m_FishName, L"Large Mouth Bass");
wcscpy( m_Location, L"Under Lily Pads");
m_bEatsFish = TRUE;
m_bFreshWater = TRUE;
STDMETHODIMP CAltCustomBass1::GetFishName( FISH_BSTR pStr)
// TRACE("CAltCustomBass1::GetFishName\n");
if (pStr)
wcscpy(pStr, m_FishName);
STDMETHODIMP CAltCustomBass1::IsFreshwater( BOOL *pBool )
// TRACE("CAltCustomBass1::IsFreshwater\n");
if (pBool)
*pBool = m_bFreshWater;
return S_OK;
// CBass:Fish implementation of IFish STDMETHODIMP CAltCustomBass1::GetLocation( FISH_BSTR pStr)
// TRACE("CAltCustomBass1::GetLocation\n");
if (pStr)
wcscpy(pStr, m_Location);
STDMETHODIMP CAltCustomBass1::SetLocation( FISH_BSTR pStr)
// TRACE("CAltCustomBass1::SetLocation\n");
if (pStr)
wcscpy(m_Location, pStr);
STDMETHODIMP CAltCustomBass1::EatsOtherFish( BOOL *pBool )
// TRACE("CAltCustomBass1::EatsOtherFish\n");
if (pBool)
*pBool = m_bEatsFish;
return S_OK;
// return E_BADPOINTER;

Again, note that the ATL Template Library implements the IUnknown interface for you, resulting in much fewer coding requirements for the developer. Now that the method implementation is complete, the COM server can be compiled and built.

Using Object Maps to Specify COM Objects

The ATL library uses object maps to specify the COM objects that make up a particular ATL server. Object maps are arrays of structures that tell ATL about the objects implemented in a server. The members of an object map include the CLSID of the object and the class of the object.

When a specific interface is requested through the QueryInterface method, ATL uses COM maps to map interface IDs (IIDs) to offsets in the interface. COM maps are used by the class CComObjectRoot. When a user calls QueryInterface(), the ATL library internally calls InternalQueryInterface() to return an interface pointer based on an IID passed in.

Thirteen different types of entries can reside in a COM map (see Table 13.3).
Table 13.3 Types of Entries
COM Entry Type Description
COM_INTERFACE_ENTRY Interface where only the class name needs to be in. The IID is synthesized by prepending IID_ to the class name. This is the basic and popular form of COM interfaces.
COM_INTERFACE_ENTRY_IID Interface where the IID and the class name need to be passed in, for example, COM_INTERFACE_ENTRY_IID (IID_IFish, IFish).
COM_INTERFACE_ENTRY2 Interface where it is necessary to distinguish conflicting interfaces. For example, if you have dual interfaces (IFoo and IBar) in an object, specifying COM_INTERFACE_ENTRY (IDispatch) would be ambiguous because both IFoo and IBar derive from Idispatch. However, by specifying COM_INTERFACE_ENTRY2 (IDispatch, IFoo), you can control which interface is returned.
COM INTERFACE_ENTRY2_IID Interface where the IID and the class name need to be passed in and you need to disambiguate interfaces.
COM_INTERFACE_ENTRY_TEAR_OFF Interface is a tear-off interface. Tear-off interfaces are generally created each time a client calls QueryInterface for a particular interface, even if a tear-off for that interface is already instantiated.
COM_INTERFACE_ENTRY_CACHED_TEAR_OFF Interface is a tear-off interface; however, ATL creates an object implementing the tear-off interface only the first time the interface is requested. Subsequent QueryInterface calls will reuse the object previously instantiated.
COM_INTERFACE_ENTRY_AGGREGATE Indicates that a QueryInterface call for a particular interface should go through the aggregate.
COM_INTERFACE_ENTRY_AGGREGATE Indicates that a QueryInterface call for an interface _BLIND should be blindly forwarded to the aggregate.
COM_INTERFACE_ENTRY_AUTOAGGREGATE Automatically creates the aggregate when a client performs a QueryInterface for a particular interface on the aggregate.
COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND Automatically creates the aggregate when a client calls QueryInterface for an interface that cannot be found on the outer object.
COM_INTERFACE_ENTRY_CHAIN Chains to the COM map of a base class.
COM_INTERFACE_ENTRY_FUNC Allows you to programmatically hook the creation of a particular interface pointer.
COM_INTERFACE_ENTRY_FUNC_BLIND Allows you to programmatically hook the creation of a pointer to any interface, thus not found.

When to Use the ActiveX Template Librarys

The ActiveX template library offers many advantages to builders of COM interfaces and COM objects. The ATL library offers the advantage of building fast, lightweight COM servers. However, using ATL may not be the best method of implementation depending on the situation.

ATL is focused entirely on the creation of small, fast COM servers in C++. ATL is optimized for the creation of objects that expose custom or dual-interfaces and has absolutely no inherent support for more complex COM-based architectures, such as ActiveX documents.

If you want to create generic COM objects or OLE automation objects with dual-interface support or you want to support COM's free-threading model (available with Windows NT 4.0 and later versions of Windows) and you don't have a significant user interface in your object, ATL is the choice for producing the smallest, fastest code.

If you want to create complex servers that need to support user-interface items, ActiveX controls, or ActiveX documents, then a more robust framework such as MFC should be used.

From Here...

This chapter has illustrated some of the many benefits gained when using the ActiveX Template Library. The ATL COM AppWizard was used to create a framework for a COM server. The New Class dialog was used to create a COM object with multiple custom interfaces. All that the user must implement is the custom functionality of the server. Chapter 12 uses the MFC framework to create COM servers. MFC is a feature-rich application framework that can be used for building COM servers. Chapter 14 illustrates a custom COM architecture for building COM servers. The custom architecture is not derived from ATL or MFC.