Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Commonly Used ATL Classes

The ATL class library includes a number of classes that are used to simplify COM programming. ATL classes can be separated into two categories:

High-Level ATL Classes

Here are some of the most commonly used high-level classes:

ATL Helper Classes

The AtlBase.h header file includes a number of helper classes that simplify the use of frequently used objects. The most commonly used helper classes follow:

Using CRegKey to Access the Registry

The CRegKey class is used to encapsulate access to the System Registry. CRegKey properly manages any HKEY handles that are used to access Registry locations. The most commonly used CRegKey member functions follow:

Listing 29.3 shows an example of using CRegKey.

Example 29.3. Using CRegKey to Read a Location from the System Registry

CRegKey regKey;
TCHAR   szBuff[1024];
DWORD   dwSize = sizeof(szBuff)/sizeof(szBuff[0]);
TCHAR   szPath[] = _T("SYSTEM\\CurrentControlSet\\Services\\"
  "codevtestsvc\\parameters\\");

LONG err = regKey.Create(HKEY_LOCAL_MACHINE, szPath);
if(!err)
{
    regKey.QueryValue(szBuff, _T("PingCounter"), &dwSize);
}

In Listing 29.3, CRegKey is used to open a Registry key and then extract the PingCounter value. Note that the size of the string buffer is passed to QueryValue as a pointer to a DWORD. When QueryValue returns, the number of bytes copied into the buffer is stored in this variable.

Using CComBstr to Manage COM Strings

The CComBstr class is used to manage COM's string objects, which are known as BSTRs. When designing interfaces, you are free to use either the BSTR or LPOLESTR types to pass strings between components. If you want to interact with Visual Basic or VBScript users, however, you must expose your strings as the BSTR type.

A BSTR is similar but not identical to the LPOLESTR type. Both types are used to represent wide (16-bit) strings; in fact, a typedef in the wtypes.h header file causes a compiler to treat the two types identically:

typedef OLECHAR __RPC_FAR *BSTR;

Unfortunately, the two types are not equivalent—the BSTR type is actually a pointer to a wide string, with 16 bits reserved before the beginning of the string that contain the string's length. Visual Basic and other languages use the length to optimize string handling.

Because of the typedef in wtypes.h, it's impossible for your compiler to warn you when you interchange LPOLESTR and BSTR. For this reason, it's a good idea to pick one method of representing your strings and to use that method exclusively. The CComBstr class is an easy way to standardize your code toward using the COM BSTR type.

CComBstr has many of the same operators found in other commonly used string classes, such as the standard C++ library string or the MFC CString. CComBstr instances can be initialized from narrow or wide character arrays:

CComBstr bstrName("Mickey");
CComBstr bstrCar(L"Rover");
							

Using the CComVariant Class

COM uses the VARIANT structure to provide a generic wrapper around commonly used types. Internally, the VARIANT is a union, with a discriminator member variable that describes the particular value contained in the union.

Listing 29.4 provides a simplified and significantly shortened version of the VARIANT declaration. The full declaration of the VARIANT structure is located in the OAIdl.h header file.

Example 29.4. A Simplified Declaration of the COM VARIANT Structure

typedef struct tagVARIANT VARIANT;
struct tagVARIANT
{
    union
    {
        struct __tagVARIANT
        {
  VARTYPE vt;
  union
  {
      ULONGLONG       ullVal;
      LONGLONG        llVal;
      LONG  lVal;
      BYTE  bVal;
      SHORT iVal;
      FLOAT fltVal;
      DOUBLEdblVal;
      .
      .
      .
      // Some declarations omitted for clarity
      .
      .
      .
  };
        };
        DECIMAL decVal;
    };
};

VARIANT structures originally were designed to be used with Visual Basic 3.0 and OLE Automation. Because Visual Basic 3.0 had a weak type system, the VARIANT structure and the IDispatch Automation interface were used for COM interaction with Visual Basic. Today the only COM clients that require Automation interfaces and VARIANT arguments are scripting clients such as VBScript.

Because of the many types of data that can be held in a VARIANT, proper handling of the structure can be difficult. Consider the (relatively) simple act of clearing the data stored in a variant. If the variant contains a simple type, such as a long or a short, the contents of the VARIANT can simply be set to zero. If the VARIANT contains an interface pointer, however, the Release method must be called before the pointer is destroyed, or the reference count for the interface will never reach zero, and the interface might never be released. Other complex types, such as BSTR, have similar requirements.

To simplify working with VARIANT structures, a number of SDK functions are designed to safely initialize, clear, copy, and convert VARIANT structures. Unfortunately, this requires the programmer to be constantly vigilant, avoiding code such as the following:

HRESULT WrongWayToCopyVariants(VARIANT* pvTarget, VARIANT* pvSource)
{
    if(!pvTarget || !pvSource)
        return E_INVALIDARG;
    *pvTarget = *pvSource;
    return S_OK;
}

The preferred way to copy a variant is to use the VariantCopy function:

HRESULT MyCopyVariantFunc(VARIANT* pvTarget, VARIANT* pvSource)
{
    if(!pvTarget || !pvSource)
        return E_INVALIDARG;
    return VariantCopy(pvTarget, pvSource);
}

In fact, even the preceding code isn't safe because it assumes the code that calls the MyCopyVariantFunc function has properly initialized the VARIANT structure with a call to VariantInit:

VARIANT v;
VariantInit(&v);

A VARIANT that has not been initialized properly by calling VariantInit is not safe to use and can easily lead to bugs that are difficult to trace.

Because of the rigid handling required for the VARIANT type, the CComVariant class was a welcome addition to ATL. The CComVariant class ensures that its internal VARIANT is properly initialized and maintained, even during copies and assignments. Further, the class properly releases data stored in the VARIANT when an instance of the class is destroyed.

Using Smart Pointers

The CComPtr and CComQIPtr classes are used to provide smart pointer wrappers around COM interface pointers. In this context, a smart pointer is a pointer that properly releases its reference counts toward COM interfaces when it goes out of scope. The COM smart pointer classes also work correctly when used with C++ exception handling (although the classes do not use exception handling).

Like most other (non-COM) smart pointer classes, CComPtr and CComQIPtr override C++ operators to make the smart pointer class act like a normal C++ pointer. CComPtr and CComQIPtr overload the following operators:

  • operator-> Returns the wrapped interface pointer, protected from misuse.
  • operator& Returns the address of the wrapped interface pointer. Asserts in debug builds if the internal pointer is NULL.
  • operator! Returns true if the wrapped interface pointer is NULL.
  • operator* De-references the wrapped interface pointer and returns the interface. Asserts in debug builds if the internal pointer is NULL.
  • operator T* Returns a pointer to the wrapped interface pointer.
  • operator= Assigns the smart pointer to a new interface pointer, calling AddRef and Release automatically as required.
  • operator== Tests the wrapped interface pointer for equality against another interface pointer. This operator does not check to see whether the two interfaces belong to the same COM object.
  • operator< Tests the wrapped interface pointer against another interface pointer.

To use CComPtr, you instantiate it with the interface type that you'll be accessing:

CComPtr<IShellLink>  pShellLink;

As discussed earlier, the CComPtr and CComQIPtr classes manage the calls to AddRef and Release automatically. The classes use an interesting template trick to prevent a client from accidentally calling these functions through operator->. Instead of returning a raw pointer to the internal interface pointer, the following code is used:

_NoAddRefReleaseOnCComPtr<T>* operator->() const
{
    ATLASSERT(p!=NULL);
    return (_NoAddRefReleaseOnCComPtr<T>*)p;
}

The implementation of the _NoAddRefReleaseOnComPtr class makes the AddRef and Release member functions private, preventing a client from writing code like this:

CComPtr<IShellLink>  pShellLink;
HRESULT hr = pShellLink.CoCreateInstance(CLSID_ShellLink);
pShellLink->Release(); // Error, won't compile!

If you find the need to decrement the reference count for an interface wrapped with CComPtr or CComQIPtr, use the smart pointer's Release member function:

CComPtr<IShellLink>  pShellLink;
HRESULT hr = pShellLink.CoCreateInstance(CLSID_ShellLink);
pShellLink.Release(); // Explicit call to smart pointer class, okay

As discussed in the preceding list, operator== tests two interfaces to see whether they are equivalent. This operator only tests the interfaces, not the underlying objects. If you need to test two interfaces to determine whether they refer to the same object, use the smart pointer's IsEqualObject member function, which compares IUnknown interface pointers, performing QueryInterface if necessary.

You can assign an interface pointer to your smart pointer in four ways:

  • Call the smart pointer's CoCreateInstance member function.
  • Pass the interface's raw interface pointer to CoCreateInstance.
  • Call the smart pointer's Attach member function to assign a previously created interface pointer to the smart pointer. This does not cause the smart pointer to call AddRef through the new interface pointer.
  • Simple assignment through the smart pointer's assignment operator causes the smart pointer to call AddRef through the new interface pointer.

Calling the smart pointer's CoCreateInstance member function is the simplest way to create a new interface pointer, but the member function uses the __uuidof() compiler extension, which requires that the interface type have a GUID defined with __declspec(uuid) as part of the interface declaration. Most standard Windows COM interfaces do have declarations, which can be added to your project by including the comdefs.h header file. Be aware that some interfaces that exist in the standard Windows headers (such as some Active Directory interfaces) cannot be created using the smart pointer's CoCreateInstance method unless you create the appropriate __declspec declaration yourself.

For interfaces that do have a GUID defined, using the smart pointer's CoCreateInstance function is straightforward and the simplest way to create a new interface pointer:

CComPtr<IShellLink>     pShellLink;
HRESULT hr = pShellLink.CoCreateInstance(CLSID_ShellLink);

To set a smart pointer to the value of a pointer returned from a function, you also can use the & operator, which works just like the non-smart pointer case:

CComPtr<IShellLink> pShellLink;
HRESULT hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_ALL,
IID_IShellLink,
reinterpret_cast<void**>(&pShellLink));

In the preceding case, the smart pointer automatically calls Release through the interface pointer when the pShellLink destructor is called.

Using the smart pointer's Attach method is appropriate in cases where you already have an interface pointer that has been AddRef'd, and you want to use the smart pointer to manage the interface's lifetime:

CComPtr<IShellLink> pShellLink;
IShellLink psl = GetShellPointer();
pShellLink.Attach(psl);

The final way to set the value of a smart pointer is to simply use the assignment operator, just as when assigning any built-in C++ type:

CComPtr<IShellLink>  pShellLink;
CComPtr<IShellLink>  pOtherShellLink;
HRESULT hr = pShellLink.CoCreateInstance(CLSID_ShellLink);
pOtherShellLink = pShellLink;

Listing 29.5 shows how the CComPtr and CComQIPtr classes are used to simplify client-side COM code.

Example 29.5. Using ATL Smart Pointer Classes to Manage Interfaces

#define _UNICODE
#define UNICODE
#include <atlbase.h>
#include <shlobj.h>
#include <comdef.h>
HRESULT BuildShortcutLink(LPCTSTR pszTargetPath, LPCTSTR pszLinkPath, LPCTSTR pszDesc);

// Demonstrates using ATL smart pointers for interface management.
// Call as:
// SmartPtr <target path> <shortcut path> <description>
int wmain(int argc, wchar_t* argv[])
{

    if(argc != 4)
        return 1;

    HRESULT hr = CoInitialize(NULL);
    if(FAILED(hr))
        wprintf(L"CoInitialize failed with HRESULT %X\n", hr);
    hr = BuildShortcutLink(argv[1], argv[2], argv[3]);
    if(FAILED(hr))
        wprintf(L"Failed with HRESULT %X\n", hr);
    else
        wprintf(L"Link to %s created at %s", argv[1], argv[2]);
    CoUninitialize();

    return 0;
}

HRESULT BuildShortcutLink(LPCTSTR pszTargetPath,
      LPCTSTR pszLinkPath,
      LPCTSTR pszDesc)
{
    CComPtr<IShellLink>  pShellLink;
    HRESULT hr = pShellLink.CoCreateInstance(CLSID_ShellLink);

    if(FAILED(hr))
        return hr;

    // Set the path to the link's target - this is not the
    // location of the shortcut, it's the file or location the
    // link refers to.
    hr = pShellLink->SetPath(pszTargetPath);
    if(FAILED(hr))
        return hr;

    // Set the description property for the link.
    hr = pShellLink->SetDescription(pszDesc);
    if(FAILED(hr))
        return hr;

    // Create a new pointer for the IPersistFile interface,
    // and query through pShellLink for that interface.
    CComQIPtr<IPersistFile> pFile = pShellLink;
    if(!pFile)
        return E_NOINTERFACE;

    // Persist the shortcut at the link's desired location.
    hr = pFile->Save(pszLinkPath, TRUE);

    return hr;
}

In Listing 29.5, the CComPtr and CComQIPtr classes are used to manage the program's interface pointers. No explicit management of the interface pointer is required—the smart pointer destructors automatically release their references to COM objects when the pointers are destroyed.

Share ThisShare This

Informit Network