| Tenouk C & C++ | MFC Home | Active Template Library 3 | Active Template Library 5 | Download | Site Index |


 

 

 

 

 

 

An Introduction To Active Template Library (ATL) Part 4

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are marshaling and intro to activeX control.

  1. The Story

  2. ATL COM AppWizard Options

  3. Creating a COM Class

  4. Apartments and Threading

  5. Connection Points and ISupportErrorInfo

  6. The Free-Threaded Marshaler

  7. Implementing the Spaceship Class Using ATL

  8. Basic ATL Architecture

  9. Managing VTBL Bloat

  10. ATL's IUnknown: CComObjectRootEx

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The Story

 

As always, start by selecting New from the File in Visual C++. This opens the New dialog with the Projects tab activated, as shown in Figure 17. Select ATL COM AppWizard from the Projects tab. Give your project a useful name such as spaceshipsvr, and click OK.

 

Figure 17: Selecting ATL COM AppWizard from the New dialog box.

 

Figure 17: Selecting ATL COM AppWizard from the New dialog box.

 

 

ATL COM AppWizard Options

 

In the Step 1 dialog, shown in Figure 18, you can choose the server type for your project from a list of options. The ATL COM AppWizard gives you the choice of creating a Dynamic Link Library (DLL), an Executable (EXE), or a Service (EXE). If you select the DLL option, the options for attaching the proxy/stub code to the DLL and for including MFC in your ATL project will be activated.

 

Figure 18: Step 1 of the ATL COM AppWizard.

 

Figure 18: Step 1 of the ATL COM AppWizard.

 

Selecting DLL as the server type produces all the necessary pieces to make your server DLL fit into the COM environment. Among these pieces are the following well-known COM functions: DllGetClassObject, DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer. Also included are the correct server lifetime mechanisms for a DLL.

If you decide you might want to run your DLL out of process as a surrogate, selecting the Allow Merging Of Proxy/Stub Code option permits you to package all your components into a single binary file. Proxy/stub code has traditionally shipped as a separate DLL. That way you have to distribute only a single DLL. If you decide you absolutely must include MFC in your DLL, go ahead and select the Support MFC check box. MFC support includes AfxWin.h and AfxDisp.h in your StdAfx.h file and links your project to the current version of MFC's import library. While using MFC can be very convenient and almost addictive at times, beware of dependencies you're inheriting when you include MFC. You can also select Support MTS to add support for Microsoft Transaction Server.

If you elect to produce an Executable EXE server, the ATL COM AppWizard produces code that compiles to an EXE file. The EXE will correctly register the class objects with the operating system by using CoRegisterClassObject() and CoRevokeClassObject(). The project will also insert the correct code for managing the lifetime of the executable server. Finally, if you choose the Service EXE option, the ATL COM AppWizard adds the necessary service-oriented code.

Using the ATL COM AppWizard to write a lightweight COM server 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. Second, you get some boilerplate Interface Definition Language (IDL) code. The IDL file is important because as the starting point for genuine COM development, it's one of the primary files you'll focus on when writing COM classes. IDL is a purely declarative language for describing COM interfaces. Once a COM interface is described in an IDL file, a simple pass though the Microsoft Interface Definition Language (MIDL) compiler creates several more useful products. These products include:

  1. The pure abstract base classes needed to write COM classes.

  2. A type library.

  3. Source code for building the proxy stub DLL (necessary for standard COM remoting).

 

Creating a COM Class

 

Once you've created a COM server, you're ready to start piling COM classes into the server. Fortunately, there's an easy way to do that with the ATL Object Wizard, shown in Figure 20. Select New ATL Object from the Insert menu to start the ATL Object Wizard.

Using the ATL Object Wizard to generate a new object adds a C++ source file and a header file containing the new class definition and implementation to your project. In addition, the ATL Object Wizard adds an interface to the IDL code. Although the ATL Object Wizard takes care of pumping out a skeleton IDL file, you'll still need to understand IDL to some extent if you want to write effective COM interfaces (as you'll soon see).

 

Figure 19: Adding new ATL object to project.

 

Figure 19: Adding new ATL object to project.

 

Figure 20:  Using the ATL Object Wizard to insert a new ATL-based COM class into the project.

 

Figure 20:  Using the ATL Object Wizard to insert a new ATL-based COM class into the project.

 

After you choose the type of ATL object, click Next to display the ATL Object Wizard Properties dialog.

 

Figure 21: ATL object’s Name page.

 

Figure 21: ATL object’s Name page.

 

Figure 22: ATL object attribute page.

 

Figure 22: ATL object attribute page.

 

Depending on which object you choose, the Attributes tab of the ATL Object Wizard Properties dialog allows you to select the threading model for your COM class, and whether you want a dual (IDispatch-based) or a custom interface. The dialog also allows you to choose how your class will support aggregation. In addition, the Object Wizard lets you easily include the ISupportErrorInfo interface and connection points in your class. Finally, you can aggregate to the Free-Threaded Marshaler if you so choose.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Apartments and Threading

 

To figure out COM, you have to understand that COM is centered on the notion of abstraction, hiding as much information as possible from the client. One piece of information that COM hides from the client is whether COM class is thread-safe. The client should be able to use an object as it sees fit without having to worry about whether an object properly serializes access to itself, that is, properly protects access to its internal data. COM defines the notion of an apartment to provide this abstraction.

An apartment defines an execution context, or thread, that houses interface pointers. A thread enters an apartment by calling a function from the CoInitialize() family: CoInitialize(), CoInitializeEx(), or OleInitialize(). Then COM requires that all method calls to an interface pointer be executed within the apartment that initialized the pointer (in other words, from the same thread that called CoCreateInstance()). COM defines two kinds of apartments, single-threaded apartments and multithreaded apartments. Single-threaded apartments can house only one thread while multithreaded apartments can house several threads. While a process can have only one multithreaded apartment, it can have many single-threaded apartments. An apartment can house any number of COM objects.

A single-threaded apartment guarantees that COM objects created within it will have method calls serialized through the remoting layer, while a COM object created within a multithreaded apartment will not. A helpful way to remember the difference between apartments is to think of it this way: instantiating a COM object within the multithreaded apartment is like putting a piece of data into the global scope where multiple threads can get to it. Instantiating a COM object within a single-threaded apartment is like putting data within the scope of only one thread. The bottom line is that COM classes that want to live in the multithreaded apartment had better be thread-safe, while COM classes that are satisfied living in their own apartments need not worry about concurrent access to their data.

A COM object living within a different process space from its client has its method calls serialized automatically via the remoting layer. However, a COM object living in a DLL might want to provide its own internal protection (using critical sections, for example) rather than having the remoting layer protect it. A COM class advertises its thread safety to the world via a Registry setting. This named value lives in the Registry under the CLSID under HKEY_CLASSES_ROOT like this:

[HKCR\CLSID\{some GUID …}\InprocServer32]

@="C:\SomeServer.DLL"

ThreadingModel=<thread model>

The ThreadingModel can be one of four values: Single, Both, Free, or Apartment, or it can be blank. ATL provides support for all current threading models. Here's a rundown of what each value indicates:

  1. Single or blank indicates that the class executes in the main thread only (the first single thread created by the client).

  2. Both indicates that the class is thread-safe and can execute in both the single-threaded and multithreaded apartments. This value tells COM to use the same kind of apartment as the client.

  3. Free indicates that the class is thread-safe. This value tells COM to force the object inside the multithreaded apartment.

  4. Apartment indicates that the class isn't thread-safe and must live in its own single-threaded apartment.

 

When you choose a threading model in the ATL Object Wizard, the wizard inserts different code into your class depending upon your selection. For example, if you select the apartment model, the Object Wizard derives your class from CComObjectRootEx and includes CComSingleThreadModel as the template parameter like this:

class ATL_NO_VTABLE CAtlSpaceship :

    public CComObjectRootEx<CComSingleThreadModel>,

    public CComCoClass<CAtlSpaceship, &CLSID_AtlSpaceship>,

    public IDispatchImpl<IAtlSpaceship, &IID_IAtlSpaceship, &LIBID_SPACESHIPSVRLib>

{...};

The CComSingleThreadModel template parameter mixes in the more efficient standard increment and decrement operations for IUnknown (because access to the class is automatically serialized). In addition, the ATL Object Wizard causes the class to insert the correct threading model value in the Registry. If you choose the Single option in the ATL Object Wizard, the class uses the CComSingleThreadModel but leaves the ThreadingModel value blank in the Registry. Choosing Both or Free causes the class to use the CComMultiThreadModel template parameter, which employs the thread-safe Win32 increment and decrement operations InterlockedIncrement and InterlockedDecrement. For example, a free-threaded class definition looks like this:

class ATL_NO_VTABLE CAtlSpaceship :

    public CComObjectRootEx< CComMultiThreadModel>,

    public CComCoClass<CAtlSpaceship, &CLSID_AtlSpaceship>,

    public IDispatchImpl<IAtlSpaceship, &IID_IAtlSpaceship, &LIBID_SPACESHIPSVRLib>

{...};

Choosing Both for your threading model inserts Both as the data for the ThreadingModel value, while choosing Free uses the data value Free for the ThreadingModel value.

 

Connection Points and ISupportErrorInfo

 

Adding connection to your COM class is easy. Selecting the Support Connection Points check box causes the class to derive from IConnectionPointImpl. This option also adds a blank connection map to your class. Adding connection points (for example, an event set) to your class is simply a matter of performing the following four steps:

 

  1. Define the callback interface in the IDL file.

  2. Use the ATL proxy generator to create a proxy.

  3. Add the proxy class to the COM class.

  4. Add the connection points to the connection point map.

 

ATL also includes support for ISupportErrorInfo. The ISupportErrorInfo interface ensures that error information is propagated up the call chain correctly. OLE Automation objects that use the error-handling interfaces must implement ISupportErrorInfo. Selecting Support ISupportErrorInfo in the ATL Object Wizard dialog causes the ATL-based class to derive from ISupportErrorInfoImpl.

 

The Free-Threaded Marshaler

 

Selecting the Free Threaded Marshaler option aggregates the COM free-threaded marshaler to your class. The generated class does this by calling CoCreateFreeThreadedMarshaler() in its FinalConstruct() function. The free-threaded marshaler allows thread-safe objects to bypass the standard marshaling that occurs whenever cross-apartment interface methods are invoked, allowing threads living in one apartment to access interface methods in another apartment as though they were in the same apartment. This process speeds up cross-apartment calls tremendously. The free-threaded marshaler does this by implementing the IMarshal interface. When the client asks the object for an interface, the remoting layer calls QueryInterface(), asking for IMarshal. If the object implements IMarshal (in this case, the object implements IMarshal because the ATL Object Wizard also adds an entry into the class's interface to handle QueryInterface() requests for IMarshal) and the marshaling request is in process, the free-threaded marshaler actually copies the pointer into the marshaling packet. That way, the client receives an actual pointer to the object. The client talks to the object directly without having to go through proxies and stubs. Of course, if you choose the Free Threaded Marshaler option, all data in your object had better be thread-safe. Just be very cautious if you check this box.

 

Implementing the Spaceship Class Using ATL

 

We'll create the spaceship class using the defaults provided by the ATL Object Wizard in the ATL Object Wizard Properties dialog. For example, the spaceship class will have a dual interface, so it will be accessible from environments such as VBScript on a Web page. In addition, the spaceship class will be an apartment model object, meaning COM will manage most of the concurrency issues. The only information you need to supply to the ATL Object Wizard is a clever name. Enter a value such as AtlSpaceship in the Short Name text box on the Names tab.

 

 

Figure 23: Entering object’s name information.

 

Figure 23: Entering object’s name information.

 

You don't need to set any of the other options right now. For instance, you don't need to set the Support Connection Points option because we'll cover connections in the next module. You can always add connection points later by typing them in by hand.

If you tell the ATL Object Wizard to create a Simple Object COM class named ATLSpaceship, here's the class definition it generates:

 

class ATL_NO_VTABLE CAtlSpaceship :

    public CComObjectRootEx<CComSingleThreadModel>,

    public CComCoClass<CAtlSpaceship, &CLSID_AtlSpaceship>,

    public IDispatchImpl<IAtlSpaceship, &IID_IAtlSpaceship, &LIBID_SPACESHIPSVRLib>

{...};

 

While ATL includes quite a few COM-oriented C++ classes, those listed in the spaceship class's inheritance list above are enough to get a flavor of how ATL works. The most generic ATL-based COM objects derive from three base classes: CComObjectRoot, CComCoClass, and IDispatch. CComObjectRoot implements IUnknown and manages the identity of the class. This means 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 class definition above, CComCoClass adds the class object that knows how to create CAtlSpaceship objects. Finally, the code produced by the ATL Object 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 (which is an IDispatch interface followed by the functions defined in the IDL).

As you can see, using ATL to implement COM classes is different from using pure C++. The Tao of ATL differs from what you might 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. The functions are added automatically 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 lightweight COM services and objects, ATL is also a new means by which you can create ActiveX controls, as you'll see in the next module.

 

Basic ATL Architecture

 

If you've experimented at all with ATL, you've seen how it simplifies the process of implementing COM classes. The tool support is quite good, it's almost as easy to develop COM classes using Visual C++ 6.0 as it is to create MFC-based programs. Just use AppWizard to create a new ATL-based class. However, instead of using ClassWizard (as you would to handle messages and to add dialog box member variables), use ClassView to add new function definitions to an interface. Then simply fill in the functions within the C++ code generated by ClassView. The code generated by AppWizard includes all the necessary code for implementing your class, including an implementation of IUnknown, a server module to house your COM class, and a class object that implements IClassFactory.

Writing COM objects as we've just described is certainly more convenient than most other methods. But exactly what happens when you use the AppWizard to generate the code for you? Understanding how ATL works is important if you want to extend your ATL-based COM classes and servers much beyond what AppWizard and ClassView provide. For example, ATL provides support for advanced interface techniques such as tear-off interfaces. Unfortunately, there's no Wizard option for implementing a tear-off interface. Even though ATL supports it, you've got to do a little work by hand to accomplish the tear-off interface. Understanding how ATL implements IUnknown is helpful in this situation. Let's examine the CAtlSpaceship class in a bit more detail. Here's the entire definition:

class ATL_NO_VTABLE CAtlSpaceship :

    public CComObjectRootEx<CComSingleThreadModel>,

    public CComCoClass<CAtlSpaceship, &CLSID_AtlSpaceship>,

    public IDispatchImpl<IAtlSpaceship, &IID_IAtlSpaceship, &LIBID_SPACESHIPSVRLib>

{

public:

    CAtlSpaceship()

    {   }

 

DECLARE_REGISTRY_RESOURCEID(IDR_ATLSPACESHIP)

 

BEGIN_COM_MAP(CAtlSpaceship)

    COM_INTERFACE_ENTRY(IAtlSpaceship)

    COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

 

// IAtlSpaceship

public:

};

 

While this is ordinary vanilla C++ source code, it differs from normal everyday C++ source code for implementing a COM object in several ways. For example, while many COM class implementations derive strictly from COM interfaces, this COM class derives from several templates. In addition, this C++ class uses several odd-looking macros. As you examine the code, you'll see ATL's implementation of IUnknown, as well as a few other interesting topics, such as a technique for managing vtable bloat and an uncommon use for templates. Let's start by taking a look at the first symbol in the wizard-generated macro code: ATL_NO_VTABLE.

 

Managing VTBL Bloat

 

COM interfaces are easily expressed in C++ as pure abstract base classes. Writing COM classes using multiple inheritance (there are other ways to write COM classes) is merely a matter of adding the COM interface base classes to your inheritance list and implementing the union of all the functions. Of course, this means that the memory footprint of your COM server will include a significant amount of vtable overhead for each interface implemented by your class. That's not a big deal if you have only a few interfaces and your C++ class hierarchy isn't very deep. However, implementing interfaces this way does add overhead that tends to accumulate as interfaces are added and hierarchies deepen. ATL provides a way to cut down on some of the overhead introduced by a lot of virtual functions. ATL defines the following symbol:

#define ATL_NO_VTABLE  __declspec(novtable)

Using ATL_NO_VTABLE prevents an object's vtable (vtable) from being initialized in the constructor, thereby eliminating from the linker the vtable and all the functions pointed to by the vtable for that class. This elimination can lower the size of your COM server somewhat, provided the most-derived class does not use the novtable declspec shown above. You'll notice the size difference in classes with deep derivation lists. One caveat, however: calling virtual functions from the constructor of any object that uses this declspec is unsafe because vptr is uninitialized. The second line in the class declaration previously shown demonstrates that CAtlSpaceship derives from CComObjectRootEx. This is where you get to ATL's version of IUnknown.

 

ATL's IUnknown: CComObjectRootEx

 

While CComObjectRootEx isn't quite at the top of the ATL hierarchy, it's pretty close. The actual base class for a COM object in ATL is a class named CComObjectRootBase. (Both class definitions are located in ATLCOM.H.) Looking at CComObjectRootBase reveals the code you might expect for a C++ based COM class. ComObjectRootBase includes a DWORD member named m_dwRef for reference counting. You'll also see OuterAddRef, OuterRelease, and OuterQueryInterface to support COM aggregation and tear-off interfaces. Looking at CComObjectRootEx reveals InternalAddRef(), InternalRelease(), and InternalQueryInterface() for performing the regular native reference counting, and QueryInterface() mechanisms for class instances with object identity. Notice that CAtlSpaceship's definition shows that the class is derived from CComObjectRootEx and that CComObjectRootEx is a parameterized template class. The listing below shows the definition of CComObjectRootEx.

template <class ThreadModel>

class CComObjectRootEx : public CComObjectRootBase

{

public:

    typedef ThreadModel _ThreadModel;

    typedef _ThreadModel::AutoCriticalSection _CritSec;

    typedef CComObjectLockT<_ThreadModel> ObjectLock;

 

    ULONG InternalAddRef()

    {

        ATLASSERT(m_dwRef != -1L);

        return _ThreadModel::Increment(&m_dwRef);

    }

    ULONG InternalRelease()

    {

        ATLASSERT(m_dwRef > 0);

        return _ThreadModel::Decrement(&m_dwRef);

    }

 

    void Lock() {m_critsec.Lock();}

    void Unlock() {m_critsec.Unlock();}

private:

    _CritSec m_critsec;

};

CComObjectRootEx is a template class that varies in type based on the kind of threading model class passed in as the template parameter. In fact, ATL supports several threading models: Single-Threaded Apartments (STAs), Multi-Threaded Apartments (MTAs), and Free Threading. ATL includes three preprocessor symbols for selecting the various default threading models for your project: _ATL_SINGLE_THREADED, _ATL_APARTMENT_THREADED, and _ATL_FREE_THREADED.

Defining the preprocessor symbol _ATL_SINGLE_THREADED in stdafx.h changes the default threading model to support only one STA-based thread. This option is useful for out-of-process servers that don't create any extra threads. Because the server supports only one thread, ATL's global state can remain unprotected by critical sections and the server is therefore more efficient. The downside is that your server can support only one thread. Defining _ATL_APARTMENT_THREADED for the preprocessor causes the default threading model to support multiple STA-based threads. This is useful for apartment model in-process servers (servers supporting the ThreadingModel=Apartment Registry value). Because a server employing this threading model can support multiple threads, ATL protects its global state using critical sections. Finally, defining the _ATL_FREE_THREADED preprocessor symbol creates servers compatible with any threading environment. That is, ATL protects its global state using critical sections, and each object in the server will have its own critical sections to maintain data safety.

These preprocessor symbols merely determine which threading class to plug into CComObjectRootEx as a template parameter. ATL provides three threading model classes. The classes provide support for the most efficient yet thread-safe behavior for COM classes within each of the three contexts listed above. The three classes are CComMultiThreadModelNoCS, CComMultiThreadModel, and CComSingleThreadModel. The following listing shows the three threading model classes within ATL:

class CComMultiThreadModelNoCS

{

public:

    static ULONG WINAPI Increment(LPLONG p)

                        {return InterlockedIncrement(p);}

    static ULONG WINAPI Decrement(LPLONG p)

                        {return InterlockedDecrement(p);}

    typedef CComFakeCriticalSection AutoCriticalSection;

    typedef CComFakeCriticalSection CriticalSection;

    typedef CComMultiThreadModelNoCS ThreadModelNoCS;

};

 

class CComMultiThreadModel

{

public:

    static ULONG WINAPI Increment(LPLONG p)

                        {return InterlockedIncrement(p);}

    static ULONG WINAPI Decrement(LPLONG p)

                        {return InterlockedDecrement(p);}

    typedef CComAutoCriticalSection AutoCriticalSection;

    typedef CComCriticalSection CriticalSection;

    typedef CComMultiThreadModelNoCS ThreadModelNoCS;

};

 

class CComSingleThreadModel

{

public:

    static ULONG WINAPI Increment(LPLONG p) {return ++(*p);}

    static ULONG WINAPI Decrement(LPLONG p) {return --(*p);}

    typedef CComFakeCriticalSection AutoCriticalSection;

    typedef CComFakeCriticalSection CriticalSection;

    typedef CComSingleThreadModel ThreadModelNoCS;

};

 

Notice that each of these classes exports two static functions, Increment() and Decrement() and various aliases for critical sections.

CComMultiThreadModel and CComMultiThreadModelNoCS both implement Increment() and Decrement() using the thread-safe Win32 InterlockedIncrement() and InterlockedDecrement() functions. CComSingleThreadModel implements Increment() and Decrement() using the more conventional ++ and -- operators.

In addition to implementing incrementing and decrementing differently, the three threading models also manage critical sections differently. ATL provides wrappers for two critical sections - a CComCriticalSection (which is a plain wrapper around the Win32 critical section API) and CComAutoCriticalSection (which is the same as CComCriticalSection with the addition of automatic initialization and cleanup of critical sections). ATL also defines a "fake" critical section class that has the same binary signature as the other critical section classes but doesn't do anything. As you can see from the class definitions, CComMultiThreadModel uses real critical sections while CComMultiThreadModelNoCS and CComSingleThreadModel use the fake no-op critical sections.

So now the minimal ATL class definition makes a bit more sense. CComObjectRootEx takes a thread model class whenever you define it.  CAtlSpaceship is defined using the CComSingleThreadModel class, so it uses the CComSingleThreadModel methods for incrementing and decrementing as well as the fake no-op critical sections. Thus CAtlSpaceship uses the most efficient behavior since it doesn't need to worry about protecting data. However, you're not stuck with that model. If you want to make CAtlSpaceship safe for any threading environment, for example, simply redefine CAtlSpaceship to derive from CComObjectRootEx using CComMultiThreadModel as the template parameter. AddRef() and Release() calls are automatically mapped to the correct Increment() and Decrement() functions.

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | Active Template Library 3 | Active Template Library 5 | Download | Site Index |