Client-Side ATL Programming

There are basically two sides to ATL—client-side support and object-side support. By far the largest portion of support is on the object side because of all the code necessary to implement ActiveX controls. However, the client-side support provided by ATL turns out to be useful and interesting also. Let's take a look at the client side of ATL. Because C++ templates are the cornerstone of ATL, we'll take a little detour first to examine them.

C++ Templates

The key to understanding the Active Template Library is understanding C++ templates. Despite the intimidating template syntax, 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: when the preprocessor encounters a macro, the preprocessor looks at the macro and expands it into regular C++ code. But the problem with macros is that they are sometimes error-prone and they are never type-safe. If you use a macro and pass an incorrect parameter, the compiler won't complain but your program might very well crash. Templates, however, are like type-safe macros. When the compiler encounters a template, the compiler expands the template just as it would a macro. But because templates are type-safe, the compiler catches any type problems before the user encounters them.

Using templates to reuse code is different from what you're used to with conventional C++ development. Components written using templates reuse code by template substitution rather than by inheriting functionality from base classes. All the boilerplate code from templates is literally pasted into the project.

The archetypal 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 gets wind of your new class and says that he or she needs the exact same functionality. However, this person wants to use floating point numbers in the array. Rather than pumping out the exact same code (except for using a different type of data), you can use a C++ template.

Here's an example of how you might use templates to solve the problem described above. The following is a dynamic array implemented as a template:

template <class T> class DynArray {
public:
    DynArray();
    ~DynArray(); // clean up and do memory management
    int Add(T Element); // adds an element and does
                        //  memory management
    void Remove(int nIndex) // remove element and 
                            //  do memory management
    T GetAt(nIndex) const;
    int GetSize();
private:
    T* TArray;
    int m_nArraysize;
};

void UseDynArray() { 
    DynArray<int> intArray;
    DynArray<float> floatArray;

    intArray.Add(4);
    floatArray.Add(5.0);

    intArray.Remove(0);
    floatArray.Remove(0);

    int x = intArray.GetAt(0);
    float f = floatArray.GetAt(0);
}

As you can imagine, creating templates is useful for implementing boilerplate COM code, and templates are the mechanism ATL uses for providing COM support. The previous example is just one of the many uses available for templates. Not only are templates useful for applying type information to a certain kind of data structure, they're also useful for encapsulating algorithms. You'll see how when you take a closer look at ATL. Let's take a look at the Active Template Library to see what comes with it.

Smart Pointers

One of the most common uses of templates is for smart pointers. The traditional C++ literature calls C++'s built-in pointers "dumb" pointers. That's not a very nice name, but normal C++ pointers don't do much except point. It's often up to the client to perform details such as pointer initialization.

As an example, let's model two types of software developer using C++ classes. You can start by creating the classes: CVBDeveloper and CCPPDeveloper.

class CVBDeveloper {
public:
    CVBDeveloper() {
    }
    ~CVBDeveloper() {
        AfxMessageBox("I used VB, so I got home early.");
    }
    virtual void DoTheWork() {
        AfxMessageBox("Write them forms");
    }
};

class CCPPDeveloper {
public:
    CCPPDeveloper() {
    }
    ~CCPPDeveloper() {
        AfxMessageBox("Stay at work and fix those pointer problems");
    }
    virtual void DoTheWork() {
        AfxMessageBox("Hacking C++ code");
    }
};

The Visual Basic developer and the C++ developer both have functions for eliciting optimal performance. Now imagine some client code that looks like this:

//UseDevelopers.CPP

void UseDevelopers() {
    CVBDeveloper* pVBDeveloper;
    .
    .
    .
    // The VB Developer pointer needs 
    //  to be initialized 
    //  sometime. But what if 
    //  you forget to initialize and later 
    //  on do something like this:
    if(pVBDeveloper) {
        // Get ready for fireworks
        //  because pVBDeveloper is 
        //  NOT NULL, it points
        //  to some random data.
        c->DoTheWork();
    }
}

In this case, the client code forgot to initialize the pVBDeveloper pointer to NULL. (Of course, this never happens in real life!) Because pVBDeveloper contains a non-NULL value (the value is actually whatever happened to be on the stack at the time), the test to make sure the pointer is valid succeeds when in fact you're expecting it to fail. The client gleefully proceeds, believing all is well. The client crashes, of course, because the client is "calling into darkness." (Who knows where pVBDeveloper is pointing—probably to nothing that even resembles a Visual Basic developer.) Naturally, you'd like some mechanism for ensuring that the pointers are initialized. This is where smart pointers come in handy.

Now imagine a second scenario. Perhaps you'd like to plug a little extra code into your developer-type classes that performs some sort of operation common to all developers. For example, perhaps you'd like all the developers to do some design work before they begin coding. Consider the earlier VB developer and C++ developer examples. When the client calls DoTheWork, the developer gets right to coding without proper design, and he or she probably leaves the poor clients in a lurch. What you'd like to do is add a very generic hook to the developer classes so they make sure the design is done before beginning to code.

The C++ solution to coping with these problems is called a smart pointer. Let's find out exactly what a smart pointer is.

Giving C++ Pointers Some Brains

Remember that a smart pointer is a C++ class for wrapping pointers. By wrapping a pointer in a class (and specifically, a template), you can make sure certain operations are taken care of automatically instead of deferring mundane, boilerplate-type operations to the client. One good example of such an operation is to make sure pointers are initialized correctly so that embarrassing crashes due to randomly assigned pointers don't occur. Another good example is to make certain that boilerplate code is executed before function calls are made through a pointer.

Let's invent a smart pointer for the developer model described earlier. Consider a template-based class named SmartDeveloper:

template<class T>
class SmartDeveloper {
    T* m_pDeveloper;

public:
    SmartDeveloper(T* pDeveloper) {
        ASSERT(pDeveloper != NULL);
        m_pDeveloper = pDeveloper;
    }
    ~SmartDeveloper() {
        AfxMessageBox("I'm smart so I'll get paid.");
    }
    SmartDeveloper & 
      operator=(const SmartDeveloper& rDeveloper) {
        return *this;
    }
    T* operator->() const {
        AfxMessageBox("About to de-reference pointer. Make /
                      sure everything's okay. ");
        return m_pDeveloper;
    }
};

The SmartDeveloper template listed above wraps a pointer—any pointer. Because the SmartDeveloper class is based on a template, it can provide generic functionality regardless of the type associated with the class. Think of templates as compiler-approved macros: declarations of classes (or functions) whose code can apply to any type of data.

We want the smart pointer to handle all developers, including those using VB, Visual C++, Java, and Delphi (among others). The template <class T> statement at the top accomplishes this. The SmartDeveloper template includes a pointer (m_pDeveloper) to the type of developer for which the class will be defined. The SmartDeveloper constructor takes a pointer to that type as a parameter and assigns it to m_pDeveloper. Notice that the constructor generates an assertion if the client passes a NULL parameter to construct SmartDeveloper.

In addition to wrapping a pointer, the SmartDeveloper implements several operators. The most important one is the "->" operator (the member selection operator). This operator is the workhorse of any smart pointer class. Overloading the member selection operator is what turns a regular class into a smart pointer. Normally, using the member selection operator on a regular C++ dumb pointer tells the compiler to select a member belonging to the class or structure being pointed to. By overriding the member selection operator, you provide a way for the client to hook in and call some boilerplate code every time that client calls a method. In the SmartDeveloper example, the smart developer makes sure the work area is in order before working. (This example is somewhat contrived. In real life, you might want to put in a debugging hook, for example.)

Adding the -> operator to the class causes the class to behave like C++'s built-in pointer. To behave like native C++ pointers in other ways, smart pointer classes need to implement the other standard operators such as the de-referencing and assignment operators.

Using Smart Pointers

Using smart pointers is really no different from using the regular built-in C++ pointers. Let's start by looking at a client that uses plain vanilla developer classes:

void UseDevelopers() {
    CVBDeveloper VBDeveloper;
    CCPPDeveloper CPPDeveloper;

    VBDeveloper.DoTheWork();
    CPPDeveloper.DoTheWork();
}

No surprises here—executing this code causes the developers simply to come in and do the work. However, you want to use the smart developers—the ones that make sure the design is done before actually starting to hack. Here's the code that wraps the VB developer and C++ developer objects in the smart pointer class:

void UseSmartDevelopers { 
    CVBDeveloper VBDeveloper;
    CCPPDeveloper CPPDeveloper;

    SmartDeveloper<CVBDeveloper> smartVBDeveloper(&VBDeveloper);
    SmartDeveloper<CCPPDeveloper> smartCPPDeveloper(&CPPDeveloper);

    smartVBDeveloper->DoTheWork();
    smartCPPDeveloper->DoTheWork();
}

Instead of bringing in any old developer to do the work (as in the previous example), the client asks the smart developers to do the work. The smart developers will automatically prepare the design before proceeding with coding.

Smart Pointers and COM

While the last example was fabricated to make an interesting story, smart pointers do have useful applications in the real world. One of those applications is to make client-side COM programming easier.

Smart pointers are frequently used to implement reference counting. Because reference counting is a very generic operation, hoisting client-side reference count management up into a smart pointer makes sense.

Because you're now familiar with the Microsoft Component Object Model, you understand that COM objects expose interfaces. To C++ clients, interfaces are simply pure abstract base classes, and C++ clients treat interfaces more or less like normal C++ objects. However, as you discovered in previous chapters, COM objects are a bit different from regular C++ objects. COM objects live at the binary level. As such, they are created and destroyed using language- independent means. COM objects are created via API functions calls. Most COM objects use a reference count to know when to delete themselves from memory. Once a COM object is created, a client object can refer to it in a number of ways by referencing multiple interfaces belonging to the same COM object. In addition, several different clients can talk to a single COM object. In these situations, the COM object must stay alive for as long as it is referred to. Most COM objects destroy themselves when they're no longer referred to by any clients. COM objects use reference counting to accomplish this self-destruction.

To support this reference-counting scheme, COM defines a couple of rules for managing COM interfaces from the client side. The first rule is that creating a new copy of a COM interface should result in bumping the object's reference count up by one. The second rule is that clients should release interface pointers when they have finished with them. Reference counting is one of the more difficult aspects of COM to get right—especially from the client side. Keeping track of COM interface reference counting is a perfect use of smart pointers.

For example, the smart pointer's constructor might take the live interface pointer as an argument and set an internal pointer to the live interface pointer. Then the destructor might call the interface pointer's Release function to release the interface so that the interface pointer will be released automatically when the smart pointer is deleted or falls out of scope. In addition, the smart pointer can help manage COM interfaces that are copied.

For example, imagine you've created a COM object and you're holding on to the interface. Suppose you need to make a copy of the interface pointer (perhaps to pass it as an out parameter). At the native COM level, you'd perform several steps. First you must release the old interface pointer. Next you need to copy the old pointer to the new pointer. Finally you must call AddRef on the new copy of the interface pointer. These steps need to occur regardless of the interface being used, making this process ideal for boilerplate code. To implement this process in the smart pointer class, all you need to do is override the assignment operator. The client can then assign the old pointer to the new pointer. The smart pointer does all the work of managing the interface pointer, relieving the client of the burden.

ATL's Smart Pointers

Much of ATL's support for client-side COM development resides in a pair of ATL smart pointers: CComPtr and CComQIPtr. CComPtr is a basic smart pointer that wraps COM interface pointers. CComQIPtr adds a little more smarts by associating a GUID (for use as the interface ID) with a smart pointer. Let's start by looking at CComPtr.

CComPtr

Here's an abbreviated version of CComPtr showing its most important parts:

template <class T>
class CComPtr {
public:
    typedef T _PtrClass;
    CComPtr() {p=NULL;}
    CComPtr(T* lp) {
        if ((p = lp) != NULL) p->AddRef();
    }
    CComPtr(const CComPtr<T>& lp) {
        if ((p = lp.p) != NULL) p->AddRef();
    }
    ~CComPtr() {if (p) p->Release();}
    void Release() {if (p) p->Release(); p=NULL;}
    operator T*() {return (T*)p;}
    T& operator*() {_ASSERTE(p!=NULL); return *p; }
    T** operator&() { _ASSERTE(p==NULL); return &p; }
    T* operator->() { _ASSERTE(p!=NULL); return p; }
    T* operator=(T* lp){return (T*)AtlComPtrAssign(
                                   (IUnknown**)&p, lp);}
    T* operator=(const CComPtr<T>& lp) {
        return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p);
    }
    T* p;
};

CComPtr is a fairly basic smart pointer. Notice the data member p of type T (the type introduced by the template parameter). CComPtr's constructor performs an AddRef on the pointer while the destructor releases the pointer—no surprises here. CComPtr also has all the necessary operators for wrapping a COM interface. Only the assignment operator deserves special mention. The assignment does a raw pointer re-assignment. The assignment operator calls a function named AtlComPtrAssign:

ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp) {
    if (lp != NULL)
        lp->AddRef();
    if (*pp)
        (*pp)->Release();
    *pp = lp;
    return lp;
}

AtlComPtrAssign does a blind pointer assignment, AddRef-ing the assignee before calling Release on the assignor. You'll soon see a version of this function that calls QueryInterface.

CComPtr's main strength is that it helps you manage the reference count on a pointer to some degree.

Using CComPtr

In addition to helping you manage AddRef and Release operations, CComPtr can help you manage code layout. Looking at a bit of code will help illustrate the usefulness of CComPtr. Imagine that your client code needs three interface pointers to get the work done as shown here:

void GetLottaPointers(LPUNKNOWN pUnk){
    HRESULT hr;
    LPPERSIST pPersist; 
    LPDISPATCH pDispatch; 
    LPDATAOBJECT pDataObject;
    hr = pUnk->QueryInterface(IID_IPersist, (LPVOID *)&pPersist);
    if(SUCCEEDED(hr)) {
        hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID *) 
                                  &pDispatch);
        if(SUCCEEDED(hr)) {
            hr = pUnk->QueryInterface(IID_IDataObject, 
                                      (LPVOID *) &pDataObject);
            if(SUCCEEDED(hr)) {
                DoIt(pPersist, pDispatch, pDataObject);
                pDataObject->Release();
            }
            pDispatch->Release();
         }
         pPersist->Release();
    }
}

You could use the controversial goto statement (and risk facing derisive comments from your co-workers) to try to make your code look cleaner, like this:

void GetLottaPointers(LPUNKNOWN pUnk){
    HRESULT hr;
    LPPERSIST pPersist; LPDISPATCH pDispatch;
    LPDATAOBJECT pDataObject;

    hr = pUnk->QueryInterface(IID_IPersist, (LPVOID *)&pPersist);
    if(FAILED(hr)) goto cleanup;

    hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID *) &pDispatch);
    if(FAILED(hr)) goto cleanup;

    hr = pUnk->QueryInterface(IID_IDataObject,
                              (LPVOID *) &pDataObject);
    if(FAILED(hr)) goto cleanup;

    DoIt(pPersist, pDispatch, pDataObject);

cleanup:
    if (pDataObject) pDataObject->Release();
    if (pDispatch) pDispatch->Release();
    if (pPersist) pPersist->Release();
}

That may not be as elegant a solution as you would like. Using CComPtr makes the same code a lot prettier and much easier to read, as shown here:

void GetLottaPointers(LPUNKNOWN pUnk){
    HRESULT hr;
    CComPtr<IUnknown> persist;
    CComPtr<IUnknown> dispatch;
    CComPtr<IUnknown> dataobject;

    hr = pUnk->QueryInterface(IID_IPersist, (LPVOID *)&persist);
    if(FAILED(hr)) return;

    hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID *) &dispatch);
    if(FAILED(hr)) return;

    hr = pUnk->QueryInterface(IID_IDataObject,
                              (LPVOID *) &dataobject);
    if(FAILED(hr)) return;

    DoIt(pPersist, pDispatch, pDataObject);

    // Destructors call release...
}

At this point, you're probably wondering why CComPtr doesn't wrap QueryInterface. After all, QueryInterface is a hot spot for reference counting. Adding QueryInterface support for the smart pointer requires some way of associating a GUID with the smart pointer. CComPtr was introduced in the first version of ATL. Rather than disrupt any existing code base, Microsoft introduced a beefed-up version of CComPtr named CComQIPtr.

CComQIPtr

Here's part of CComQIPtr's definition:

template <class T, const IID* piid = &__uuidof(T)>
class CComQIPtr {
public:
    typedef T _PtrClass;
    CComQIPtr() {p=NULL;}
    CComQIPtr(T* lp) {
        if ((p = lp) != NULL)
            p->AddRef();
    }
    CComQIPtr(const CComQIPtr<T,piid>& lp) {
        if ((p = lp.p) != NULL)
            p->AddRef();
    }
    CComQIPtr(IUnknown* lp) {
        p=NULL;
        if (lp != NULL)
            lp->QueryInterface(*piid, (void **)&p);
    }
    ~CComQIPtr() {if (p) p->Release();}
    void Release() {if (p) p->Release(); p=NULL;}
    operator T*() {return p;}
    T& operator*() {_ASSERTE(p!=NULL); return *p; }
    T** operator&() { _ASSERTE(p==NULL); return &p; }
    T* operator->() {_ASSERTE(p!=NULL); return p; }
    T* operator=(T* lp){
        return (T*)AtlComPtrAssign((IUnknown**)&p, lp);
    }
    T* operator=(const CComQIPtr<T,piid>& lp) {
        return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p);
    }
    T* operator=(IUnknown* lp) {
        return (T*)AtlComQIPtrAssign((IUnknown**)&p, lp, *piid);
    }
    bool operator!(){return (p == NULL);}
    T* p;
};

What makes CComQIPtr different from CComPtr is the second template parameter, piid—the interfaces's GUID. This smart pointer has several constructors: a default constructor, a copy constructor, a constructor that takes a raw interface pointer of unspecified type, and a constructor that accepts an IUnknown interface as a parameter. Notice in this last constructor that if the developer creates an object of this type and initializes it with a plain old IUnknown pointer, CComQIPtr calls QueryInterface using the GUID template parameter. Also notice that the assignment to an IUnknown pointer calls AtlComQIPtrAssign to make the assignment. As you can imagine, AtlComQIPtrAssign performs a QueryInterface under the hood using the GUID template parameter.

Using CComQIPtr

Here's how you might use CComQIPtr in some COM client code:

void GetLottaPointers(ISomeInterface* pSomeInterface){
    HRESULT hr;
    CComQIPtr<IPersist, &IID_IPersist> persist;
    CComQIPtr<IDispatch, &IID_IDispatch> dispatch;
    CComPtr<IDataObject, &IID_IDataObject> dataobject;

    dispatch = pSomeInterface;   // implicit QI
    persist = pSomeInterface;    //  implicit QI
    dataobject = pSomeInterface; //  implicit QI

    DoIt(persist, dispatch, dataobject); // send to a function 
                                         // that needs IPersist*,
                                         // IDispatch*, and 
                                         // IDataObject*

    // Destructors call release...
}

The CComQIPtr is useful whenever you want the Java-style or Visual Basic-style type conversions. Notice that the code listed above didn't require any calls to QueryInterface or Release. Those calls happened automatically.

ATL Smart Pointer Problems

Smart pointers can be quite convenient in some places (as in the CComPtr example where we eliminated the goto statement). Unfortunately, C++ smart pointers aren't the panacea that programmers pray for to solve their reference-counting and pointer-management problems. Smart pointers simply move these problems to a different level.

One situation in which to be very careful with smart pointers is when converting from code that is not smart-pointer based to code that uses the ATL smart pointers. The problem is that the ATL smart pointers don't hide the AddRef and Release calls. This just means you need to take care to understand how the smart pointer works rather than be careful about how you call AddRef and Release.

For example, imagine taking this code:

void UseAnInterface(){
    IDispatch* pDispatch = NULL;

    HRESULT hr = GetTheObject(&pDispatch);
    if(SUCCEEDED(hr)) {
        DWORD dwTICount;
        pDispatch->GetTypeInfoCount(&dwTICount);
        pDispatch->Release();
    }
}

and capriciously converting the code to use a smart pointer like this:

void UseAnInterface() {
    CComPtr<IDispatch> dispatch = NULL;

    HRESULT hr = GetTheObject(&dispatch);
    if(SUCCEEDED(hr)) {
        DWORD dwTICount;
        dispatch->GetTypeInfoCount(&dwTICount);
        dispatch->Release();
    }
}

Because CComPtr and CComQIPtr do not hide calls to AddRef and Re-lease, this blind conversion causes a problem when the release is called through the dispatch smart pointer. The IDispatch interface performs its own release, so the code above calls Release twice—the first time explicitly through the call dispatch->Release() and the second time implicitly at the function's closing curly bracket.

In addition, ATL's smart pointers include the implicit cast operator that allows smart pointers to be assigned to raw pointers. In this case, what's actually happening with the reference count starts to get confusing.

The bottom line is that while smart pointers make some aspect of client-side COM development more convenient, they're not foolproof. You still have to have some degree of knowledge about how smart pointers work if you want to use them safely.