previous page
next page

Debugging

ATL provides a number of helpful debugging facilities, including both a normal and a categorized wrapper for producing debug output, a macro for making assertions, and debug output for tracing calls to QueryInterface, AddRef, and Release on an interface-by-interface basis. Of course, during a release build, all these debugging facilities fall away to produce the smallest, fastest binary image possible.

Making Assertions

Potentially the best debugging technique is to use assertions, which enable you to make assumptions in your code and, if those assumptions are invalidated, to be notified immediately. Although ATL doesn't exactly support assertions, it does provide the ATLASSERT macro. However, it's actually just another name for the Microsoft CRT macro _ASSERTE:

#ifndef ATLASSERT                     
#define ATLASSERT(expr) _ASSERTE(expr)
#endif                                

Flexible Debug Output

OutputDebugString is handy as the Win32 equivalent of printf, but it takes only a single string argument. We want a printf that outputs to debug output instead of standard output. ATL provides the AtlTrace function to do exactly that:

inline void _cdecl AtlTrace(LPCSTR pszFormat, ...) 
inline void _cdecl AtlTrace(LPCWSTR pszFormat, ...)

Instead of calling the function directly, use the macro ATLTRACE. The macro calls the underlying function, but also adds file and line number information to the trace output. The macro expands to either a call to AtlTrace or nothing, depending on whether the _DEBUG symbol is defined. Typical usage is as follows:

HRESULT CPenguin::FinalConstruct() {
  ATLTRACE(__TEXT("%d+%d= %d\n"), 2, 2, 2+2);
}

ATLTRACE always generates output to the debug window. If you'd like to be even more selective about what makes it to debug output, ATL provides a second trace function, AtlTrace2, also with its own macro, ATLTRACE2:

void AtlTrace2(DWORD_PTR dwCategory, UINT nLevel,
    LPCSTR pszFormat, ...)                       
void AtlTrace2(DWORD_PTR dwCategory, UINT nLevel,
    LPCWSTR pszFormat, ...)                      

In addition to the format string and the variable arguments, AtlTrace2 takes a trace category and a trace level. The trace category is defined as an instance of the CTraceCategory class. ATL includes the following trace categories, already defined:

#ifdef _DEBUG                                                   
#define DECLARE_TRACE_CATEGORY( name ) \                        
    extern ATL::CTraceCategory name;                            
#else                                                           
#define DECLARE_TRACE_CATEGORY( name ) const DWORD_PTR name = 0;
#endif                                                          
                                                                
DECLARE_TRACE_CATEGORY( atlTraceGeneral )                       
DECLARE_TRACE_CATEGORY( atlTraceCOM )                           
DECLARE_TRACE_CATEGORY( atlTraceQI )                            
DECLARE_TRACE_CATEGORY( atlTraceRegistrar )                     
DECLARE_TRACE_CATEGORY( atlTraceRefcount )                      
DECLARE_TRACE_CATEGORY( atlTraceWindowing )                     
DECLARE_TRACE_CATEGORY( atlTraceControls )                      
DECLARE_TRACE_CATEGORY( atlTraceHosting )                       
DECLARE_TRACE_CATEGORY( atlTraceDBClient )                      
DECLARE_TRACE_CATEGORY( atlTraceDBProvider )                    
DECLARE_TRACE_CATEGORY( atlTraceSnapin )                        
DECLARE_TRACE_CATEGORY( atlTraceNotImpl )                       
DECLARE_TRACE_CATEGORY( atlTraceAllocation )                    
DECLARE_TRACE_CATEGORY( atlTraceException )                     
DECLARE_TRACE_CATEGORY( atlTraceTime )                          
DECLARE_TRACE_CATEGORY( atlTraceCache )                         
DECLARE_TRACE_CATEGORY( atlTraceStencil )                       
DECLARE_TRACE_CATEGORY( atlTraceString )                        
DECLARE_TRACE_CATEGORY( atlTraceMap )                           
DECLARE_TRACE_CATEGORY( atlTraceUtil )                          
DECLARE_TRACE_CATEGORY( atlTraceSecurity )                      
DECLARE_TRACE_CATEGORY( atlTraceSync )                          
DECLARE_TRACE_CATEGORY( atlTraceISAPI )                         
                                                                
// atlTraceUser categories are no longer needed.                
// Just declare your own trace category using CTraceCategory.   
DECLARE_TRACE_CATEGORY( atlTraceUser )                          
DECLARE_TRACE_CATEGORY( atlTraceUser2 )                         
DECLARE_TRACE_CATEGORY( atlTraceUser3 )                         
DECLARE_TRACE_CATEGORY( atlTraceUser4 )                         
                                                                
#pragma deprecated( atlTraceUser )                              
#pragma deprecated( atlTraceUser2 )                             
#pragma deprecated( atlTraceUser3 )                             
#pragma deprecated( atlTraceUser4 )                             

The CTraceCategory class associates the category name with the underlying value so that it appears in the trace listing. The four atlTraceUserX categories exist for backward-compatibility; ATL versions 7 and earlier had no means of defining custom trace categories. For new code, you simply need to create a global instance of CTraceCategory like this:

CTraceCategory PenguinTraces( "CPenguin trace", 1 );
...
STDMETHODIMP CPenguin::Fly() {
    ATLTRACE2(PenguinTraces,   2,
        _T("IBird::Fly\n"));
    ATLTRACE2(PenguinTraces,   42,
        _T("Hmmm... Penguins can't fly...\n"));
    ATLTRACE2(atlTraceNotImpl, 0,
        _T("IBird::Fly not implemented!\n"));
    return E_NOTIMPL;
}

The trace level is a measure of severity, with 0 the most severe. ATL itself uses only levels 0 and 2. The documentation recommends that you stay between 0 and 4, but you can use any level up to 4,294,967,295 (although that might be a little too fine grained to be useful).

Also, because ATL uses atlTraceNotImpl so often, there's even a special macro for it:

#define ATLTRACENOTIMPL(funcname) \               
  ATLTRACE2(atlTraceNotImpl, 2, \                 
    _T("ATL: %s not implemented.\n"), funcname); \
  return E_NOTIMPL                                

This macro is used a lot in the implementations of the OLE interfaces:

STDMETHOD(SetMoniker)(DWORD, IMoniker*) {             
    ATLTRACENOTIMPL(_T("IOleObjectImpl::SetMoniker"));
}                                                     

Tracing Calls to QueryInterface

ATL's implementation of QueryInterface is especially well instrumented for debugging. If you define the _ATL_DEBUG_QI symbol before compiling, your objects will output their class name, the interface being queried for (by name[10], if available), and whether the query succeeded or failed. This is extremely useful for reverse engineering clients' interface requirements. For example, here's a sample of the _ATL_DEBUG_QI output when hosting a control in IE6:

[10] Interface names for remotable interfaces are available in the Registry as the default value of the HKEY_CLASSES_ROOT\{IID} key.

CComClassFactory - IUnknown
CComClassFactory - IClassFactory
CComClassFactory - IClassFactory
CComClassFactory -  - failed
CPenguin - IUnknown
CPenguin -  - failed
CPenguin - IOleControl
CPenguin - IClientSecurity - failed
CPenguin - IQuickActivate
CPenguin - IOleObject
CPenguin - IViewObjectEx
CPenguin - IPointerInactive - failed
CPenguin - IProvideClassInfo2
CPenguin - IConnectionPointContainer - failed
CPenguin - IPersistPropertyBag2 - failed
CPenguin - IPersistPropertyBag - failed
CPenguin - IPersistStreamInit
CPenguin - IViewObjectEx
CPenguin - IActiveScript - failed
CPenguin -  - failed
CPenguin - IOleControl
CPenguin - IOleCommandTarget - failed
CPenguin - IDispatchEx - failed
CPenguin - IDispatch
CPenguin - IOleControl
CPenguin - IOleObject
CPenguin - IOleObject
CPenguin - IRunnableObject - failed
CPenguin - IOleObject
CPenguin - IOleInPlaceObject
CPenguin - IOleInPlaceObjectWindowless
CPenguin - IOleInPlaceActiveObject
CPenguin - IOleControl
CPenguin - IClientSecurity - failed

Tracing Calls to AddRef and Release

The only calls more heavily instrumented for debugging than QueryInterface are AddRef and Release. ATL provides an elaborate scheme for tracking calls to AddRef and Release on individual interfaces. It is elaborate because each ATL-based C++ class has a single implementation of AddRef and Release, implemented in the most derived classfor example, CComObject. To overcome this limitation, when _ATL_DEBUG_INTERFACES is defined, ATL wraps each new interface[11] handed out via QueryInterface in another C++ object that implements a single interface. Each of these "thunk objects" keeps track of the real interface pointer, as well as the name of the interface and the name of the class that has implemented the interface. The thunk objects also keep track of an interface pointerspecific reference count that is managed, along with the object's reference count, in the thunk object's implementation of AddRef and Release. As calls to AddRef and Release are made, each thunk object knows exactly which interface is being used and dumps reference count information to debug output. For example, here's the same interaction between a control and IE6, but using _ATL_DEBUG_INTERFACES instead of _ATL_DEBUG_QI:

[11] ATL makes sure to always hand out the same thunk for each object's IUnknown* to observe the rules of COM identity as discussed in Chapter 5, "COM Servers."

QIThunk-1   AddRef:   Object=0x021c2c88   Refcount=1   CComClassFactory-IUnknown
IThunk-2    AddRef:   Object=0x021c2c88   Refcount=1   CComClassFactory-IClassFactory
QIThunk-2   AddRef:   Object=0x021c2c88   Refcount=2   CComClassFactory-IClassFactory
QIThunk-2   Release:  Object=0x021c2c88   Refcount=1   CComClassFactory-IClassFactory
QIThunk-3   AddRef:   Object=0x021c2c88   Refcount=1   CComClassFactory-IClassFactory
QIThunk-2   Release:  Object=0x021c2c88   Refcount=0   CComClassFactory-IClassFactory
QIThunk-4   AddRef:   Object=0x021c2e38   Refcount=1   CPenguin-IUnknown
QIThunk-5   AddRef:   Object=0x021c2e40   Refcount=1   CPenguin-IOleControl
QIThunk-5   Release:  Object=0x021c2e40   Refcount=0   CPenguin-IOleControl
QIThunk-6   AddRef:   Object=0x021c2e60   Refcount=1   CPenguin-IQuickActivate
QIThunk-7   AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-8   AddRef:   Object=0x021c2e4c   Refcount=1   CPenguin-IViewObjectEx
QIThunk-9   AddRef:   Object=0x021c2e68   Refcount=1   CPenguin-IProvideClassInfo2
QIThunk-9   Release:  Object=0x021c2e68   Refcount=0   CPenguin-IProvideClassInfo2
QIThunk-8   Release:  Object=0x021c2e4c   Refcount=0   CPenguin-IViewObjectEx
QIThunk-7   Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-6   Release:  Object=0x021c2e60   Refcount=0   CPenguin-IQuickActivate
QIThunk-10  AddRef:   Object=0x021c2e3c   Refcount=1   CPenguin-IPersistStreamInit
QIThunk-10  Release:  Object=0x021c2e3c   Refcount=0   CPenguin-IPersistStreamInit
QIThunk-11  AddRef:   Object=0x021c2e4c   Refcount=1   CPenguin-IViewObjectEx
QIThunk-12  AddRef:   Object=0x021c2e40   Refcount=1   CPenguin-IOleControl
QIThunk-12  Release:  Object=0x021c2e40   Refcount=0   CPenguin-IOleControl
QIThunk-13  AddRef:   Object=0x021c2e38   Refcount=1   CPenguin-IDispatch
QIThunk-14  AddRef:   Object=0x021c2e40   Refcount=1   CPenguin-IOleControl
QIThunk-14  Release:  Object=0x021c2e40   Refcount=0   CPenguin-IOleControl
QIThunk-3   Release:  Object=0x021c2c88   Refcount=0   CComClassFactory-IClassFactory
QIThunk-15  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-16  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-16  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-15  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-17  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-18  AddRef:   Object=0x021c2e50   Refcount=1   CPenguin-IOleInPlaceObject
QIThunk-19  AddRef:   Object=0x021c2e50   Refcount=1   CPenguin-IOleInPlaceObjectWindowless
QIThunk-20  AddRef:   Object=0x021c2e48   Refcount=1   CPenguin-IOleInPlaceActiveObject
QIThunk-20  Release:  Object=0x021c2e48   Refcount=0   CPenguin-IOleInPlaceActiveObject
QIThunk-18  Release:  Object=0x021c2e50   Refcount=0   CPenguin-IOleInPlaceObject
QIThunk-19  AddRef:   Object=0x021c2e50   Refcount=2   CPenguin-IOleInPlaceObjectWindowless
QIThunk-17  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-19  Release:  Object=0x021c2e50   Refcount=1   CPenguin-IOleInPlaceObjectWindowless
QIThunk-21  AddRef:   Object=0x021c2e40   Refcount=1   CPenguin-IOleControl
QIThunk-21  Release:  Object=0x021c2e40   Refcount=0   CPenguin-IOleControl
QIThunk-22  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-23  AddRef:   Object=0x021c2e50   Refcount=1   CPenguin-IOleInPlaceObject
QIThunk-19  Release:  Object=0x021c2e50   Refcount=0   CPenguin-IOleInPlaceObjectWindowless
QIThunk-23  Release:  Object=0x021c2e50   Refcount=0   CPenguin-IOleInPlaceObject
QIThunk-22  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-24  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-25  AddRef:   Object=0x021c2e50   Refcount=1   CPenguin-IOleInPlaceObject
QIThunk-25  Release:  Object=0x021c2e50   Refcount=0   CPenguin-IOleInPlaceObject
QIThunk-24  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-13  Release:  Object=0x021c2e38   Refcount=0   CPenguin-IDispatch
QIThunk-11  Release:  Object=0x021c2e4c   Refcount=0   CPenguin-IViewObjectEx
QIThunk-26  AddRef:   Object=0x021c2e44   Refcount=1   CPenguin-IOleObject
QIThunk-26  Release:  Object=0x021c2e44   Refcount=0   CPenguin-IOleObject
QIThunk-4   Release:  Object=0x021c2e38   Refcount=0   CPenguin-IUnknown
QIThunk-1   Release:  Object=0x021c2c88   Refcount=0   CComClassFactory-IUnknown

ATL maintains a list of outstanding thunk objects. This list is used at server shutdown to detect any leaksthat is, any interfaces that the client has not released. When using _ATL_DEBUG_INTERFACES, watch your debug output for the string LEAK, which is an indication that someone has mismanaged an interface reference:

ATL: QIThunk - 4 LEAK: Object = 0x00962920 Refcount = 4
  MaxRefCount = 4 CCalc - ICalc

The most useful part of this notification is the index of QI thunk object. You can use this to track when the leaked interface is acquired by using the CAtlDebugInterfacesModule class. This is the class that manages the thunk objects during debug builds, and a global instance of this class called _AtlDebugInterfacesModule is automatically included in your class when the _ATL_DEBUG_INTERFACES symbol is defined. You can instruct the debugger to break at the appropriate time by setting the m_nIndexBreakAt member of the CAtlDebugInterfacesModule at server start-up time.

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,
    LPVOID lpReserved) {
    hInstance;
    BOOL b = _AtlModule.DllMain(dwReason, lpReserved);
    // Trace down interface leaks
#ifdef _ATL_DEBUG_INTERFACES
    _AtlDebugInterfacesModule.m_nIndexBreakAt = 4;
#endif
    return b;
}

When that interface thunk is allocated,_AtlDebugInterfacesModule calls DebugBreak, handing control over to the debugger and allowing you to examine the call stack and plug the leak.

_ATL_DEBUG_REFCOUNT

Versions of ATL earlier than version 3 used the _ATL_DEBUG_REFCOUNT symbol to track interface reference counts for ATL IXxxImpl classes only. Because _ATL_DEBUG_INTERFACES is much more general, it has replaced _ATL_DEBUG_REFCOUNT, although _ATL_DEBUG_REFCOUNT is still supported for backward compatibility.

#ifdef _ATL_DEBUG_REFCOUNT   
#ifndef _ATL_DEBUG_INTERFACES
#define _ATL_DEBUG_INTERFACES
#endif                       
#endif                       


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)