Debugging Tips

[Previous] [Next]

ATL can provide you with run-time feedback in the output window that displays the results of QueryInterface, AddRef, and Release on any interface in your server project. These features are not enabled by default, even in debug builds, and there is no way to enable them through the user interface in Microsoft Visual C++. Instead, you enable debug output by defining macros in stdafx.h. Currently two options are available: QueryInterface debugging, which is enabled by the _ATL_DEBUG_QI preprocessor, and reference-count debugging, which is enabled by the _ATL_DEBUG_INTERFACES preprocessor. Both options can be used at the same time or independently, but remember that the options apply to all objects in a server. After inserting either or both of these in stdafx.h (prior to atlbase.h), you'll need to recompile for ATL to conditionally include the debugging points.

Debugging QueryInterface

You can watch the results of QueryInterface calls by defining the _ATL_DEBUG_QI preprocessor in your server. A debug string is sent to the output window for every call to QueryInterface that occurs in the server. The string contains the name of the class followed by the name of the interface on which QueryInterface was called. If the interface name isn't found in the Registry, the interface ID is shown instead. If QueryInterface fails, the output is appended with a "failed" suffix. Figure 6-2 shows the output from _ATL_DEBUG_QI in the Visual C++ Output window when a simple object is created.

click to view at full size.

Figure 6-2. The _ATL_DEBUG_QI output.

Figure 6-3 shows a Microsoft Visual Basic application creating the object.

Figure 6-3. A Visual Basic client creating a simple ATL object.

The QueryInterface debug feature relies on all QueryInterface calls ending up in CComObjectRootBase::InternalQueryInterface, in which a macro is called to dump the results of the call. InternalQueryInterface is shown here:

 static HRESULT WINAPI InternalQueryInterface(void* pThis,     const _ATL_INTMAP_ENTRY* pEntries, REFIID iid,     void** ppvObject) {     ATLASSERT(pThis != NULL);     // First entry in the COM map should be a simple map entry     ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY); #if defined(_ATL_DEBUG_INTERFACES) || defined(_ATL_DEBUG_QI)     LPCTSTR pszClassName = (LPCTSTR) pEntries[-1].dw; #endif // _ATL_DEBUG_INTERFACES     HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries,         iid, ppvObject); #ifdef _ATL_DEBUG_INTERFACES     _Module.AddThunk((IUnknown**)ppvObject, pszClassName, iid); #endif // _ATL_DEBUG_INTERFACES     return _ATLDUMPIID(iid, pszClassName, hRes); } 

The very last line of code is the source of our output, using _ATLDUMPIID. This macro is conditionally compiled based on the presence or lack of the _ATL_DEBUG_QI macro, shown here:

 #ifdef _ATL_DEBUG_QI #define _ATLDUMPIID(iid, name, hr) AtlDumpIID(iid, name, hr) #else #define _ATLDUMPIID(iid, name, hr) hr #endif 

If you're not debugging QueryInterface, the macro simply resolves to the HRESULT returned by AtlInternalQueryInterface; otherwise, it generates a call to AtlDumpIID, passing in the interface ID, the class name, and the result of QueryInterface. AtlDumpIID outputs the class name and attempts to look up the interface ID in the Registry to get the interface name. If the name isn't found under HKCR\Interfaces or HKCR\CLSID, the string IID itself is output.

AtlDumpIID is also used for tracing reference counts on interfaces, although the method of getting there is more complex.

Reference-Count Debugging

ATL provides you with the ability to trace reference counts on a per-interface basis. To enable this feature, you just define the preprocessor _ATL_DEBUG_INTERFACES. Any time AddRef or Release is called on an interface, the reference count, class name, and interface name are sent to the Output window, as shown in Figure 6-4.

click to view at full size.

Figure 6-4. The _ATL_DEBUG_INTERFACES output.

Lines of output with a greater than symbol (>) indicate AddRef, and lines with a less than symbol (<) indicate Release. ATL generates the debug output by creating a structure for each interface supported in your object. The structure has code that intercepts calls into that interface and injects debug output into the process. The interception process begins when the client calls QueryInterface on a particular interface, resulting in a call to CComObjectRootBase::InternalQueryInterface in an ATL object, just as we saw in QueryInterface debugging. However, this time we're interested in the AddThunk call in InternalQueryInterface, shown here:

 #ifdef _ATL_DEBUG_INTERFACES     _Module.AddThunk((IUnknown**)ppvObject, pszClassName, iid); 

In this case, thunking refers to adding a level of indirection to a virtual function call sequence for the purpose of adding the debug output. After the debug output is complete, the call is forwarded to its original target function. If the call isn't to AddRef or Release, it passes through the thunk code to the target object. AddThunk creates a new _QIThunk structure for the interface being queried for if one doesn't exist for that COM identity. _QIThunk caches the interface pointer for the object and has vtable entries for all of the IUnknown methods and room for more than a thousand other methods supported by the interface. Once the _QIThunk is created, AddThunk returns IUnknown for the thunk to CComObjectRootBase. Any calls on that interface will go through the thunk first. Because ATL doesn't know the signature for any methods on the interface except the obligatory IUnknown methods, other methods are forwarded by one of the 1024 vtable placeholders in _QIThunk (minus the space for the IUnknown entries). The extra entries are declared with placeholder methods, as shown here:

 STDMETHOD(f3)();  STDMETHOD(f1023)(); STDMETHOD(f1024)(); 

Each placeholder has a corresponding implementation, defined by a matching IMPL_THUNK macro, shown here:

 IMPL_THUNK(3)  IMPL_THUNK(1023) IMPL_THUNK(1024) 

IMPL_THUNK expands to inline assembly code to forward the method call on to its correct vtable entry on the real object, like this:

 #define IMPL_THUNK(n)\ _ _declspec(naked) inline HRESULT _QIThunk::f##n()\ {\     _ _asm mov eax, [esp+4]\     _ _asm cmp dword ptr [eax+8], 0\     _ _asm jg goodref\     _ _asm call atlBadThunkCall\     _ _asm goodref:\     _ _asm mov eax, [esp+4]\     _ _asm mov eax, dword ptr [eax+4]\     _ _asm mov [esp+4], eax\     _ _asm mov eax, dword ptr [eax]\     _ _asm mov eax, dword ptr [eax+4*n]\     _ _asm jmp eax\ } 

In this function, the esp register is modified to hold the QIThunk cached pUnk interface pointer to the real object, and the vtable entry is then calculated using the offset n passed in as the macro parameter in IMPL_THUNK. This code relies on the cached IUnknown pointer to the target object (pUnk) as the first data member in _QIThunk, which puts it first in memory after the vtable pointer.

The familiar AddRef and Release methods are not handled through IMPL_THUNK. Instead, they are forwarded directly to the object and then AtlDumpIID is called on to create the debug output. _QIThunk::AddRef is shown here:

 STDMETHOD_(ULONG, AddRef)() {     if(bBreak)         DebugBreak();     pUnk->AddRef();     return InternalAddRef(); } ULONG InternalAddRef() {     if(bBreak)         DebugBreak();     ATLASSERT(m_dwRef >= 0);     long l = InterlockedIncrement(&m_dwRef);     ATLTRACE(_T("%d> "), m_dwRef);     AtlDumpIID(iid, lpszClassName, S_OK);     if(l > m_dwMaxRef)         m_dwMaxRef = l;     return l; } 

Although the IMPL_THUNK code isn't specifically used in AddRef and Release, it enables ATL to intercept AddRef and Release on a given interface by forwarding the non-IUnknown methods, which aren't of interest for reference-count debugging.



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net