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, 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:
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 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:
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
|