- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
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 classes that provide module-level or COM class-level services. These classes include CComObject and related classes, as well as classes used to implement executable modules, such as CComModule.
- Helper classes that simplify the use of commonly used COM interfaces, components, and structures, such as BSTRs, interface pointers, and variants.
High-Level ATL Classes
Here are some of the most commonly used high-level classes:
- CComModule Provides the implementation for a COM server suitable for an EXE or DLL. CComModule manages the class factory, class objects, and component registration.
- CComObject Implements the IUnknown interface for instances of your COM class. ATL also includes specialized versions of this class, such as CComObjectStack, which can be used if your COM objects will live on the stack, and CComAggObject for aggregated COM objects. The CComObject class defined for your COM class uses multiple inheritance to derive from all interfaces supported by the COM class it represents.
- CComObjectRoot A typedef made for each project that consists of CComObjectRootEx parameterized with an appropriate threading class.
- CComObjectRootEx A template that includes the basic IUnknown interface functionality used by CComObject. This template is instantiated using a threading model class as a parameter. The threading model contains functions that are implemented differently, depending on the threading model, such as AddRef and Release.
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:
- CComBstr Wraps the COM BSTR string type
- CRegKey Simplifies working with the System Registry
- CComVariant Provides a wrapper around the COM VARIANT type
- CComPtr Provides a smart pointer wrapper around COM interface pointers
- CComQIPtr Adds the capability to automatically query for the desired interface; Similar to CComPtr
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:
- Open Opens a specified key in the Registry, setting the internal HKEY to the handle of the requested key.
- Create Creates a specified key in the Registry, or opens the key if it already exists. The internal HKEY is set to the handle of the requested key.
- Close Closes the open Registry key held by the CRegKey object and sets the internal HKEY value to NULL.
- QueryValue Retrieves the contents of a value field in the Registry. The name of the value is passed as a parameter.
- SetValue Sets the contents of a value field in the Registry.
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.
Using the Interface Definition Language | Next Section

Account Sign In
View your cart