How to print function names to debug output with ATL’s ATLTRACE macro

Starting Visual Studio .NET version 2003 (2002?) ATL offered new tracing macros ATLTRACE/ATLTRACE2 which are quite convenient and flexible to generate debug output and, what is important, come with ATL debug API which allows to adjust output settings on runtime using ATL/MFC Trace Tool.

The problem however appeared to be that ATL debug API does not seem to support printing function names, although trace tools shows checbox “Category & Function Names”. I am solving this shortage.

The way to solve the problem is in updating ATL source code, atltrace.h file. One can either modify the file in ATL include directory, or, the way I prefer, is to make a copy of the file, update it and include before other ATL includes to include instead of original header file. There will be no updated file because it is based on ATL code which, I believe, requires special permission to distribute. Changes are mentioned below though.
First of all, at the very top, a replacement header file requires definition of __ATLTRACE_H__ to prevent from inclusion of original atltrace.h:

#define __ATLTRACE_H__ // This is a replacement of ATL’s atltrace.h

Then we definitely have to update ATLRACE and ATLTRACE2 macro definition to involve function name __FUNCTION__:

#define ATLTRACE ATL::CTraceFileLineFunctionInfo(__FILE__, __LINE__, __FUNCTION__)
#define ATLTRACE2 ATLTRACE

CTraceFileLineFunctionInfo is a new class based on original CTraceFileAndLineInfo to also involve __FUNCTION__ parameter and pass it through the calls to ATL::CTrace::s_trace.TraceV, which in its turn also needs an extra parameter to be added.

TraceV uses ATL debug API AtlTraceVA/AtlTraceVU call to actually print debug output. It does not show us a parameter for function name, so we have to inject it before variable list of arguments. Updated TraceV is shown below (WCHAR version is updated correspondingly):

inline VOID __cdecl CTrace::TraceV(LPCSTR pszFileName, INT nLine, LPCSTR pszFunctionName, DWORD_PTR nCategory, UINT nLevel, LPCSTR pszFormat, va_list Arguments) const
{
#if defined(_DEBUG)
CHAR pszFormatEx[4096] = { 0 };
strcpy_s(pszFormatEx, _countof(pszFormatEx), “%hs: “);
strcat_s(pszFormatEx, _countof(pszFormatEx), pszFormat);
struct { LPCSTR pszFunctionName; BYTE pnBuffer[1024 – sizeof (LPCSTR)]; } ArgumentsEx;
ArgumentsEx.pszFunctionName = pszFunctionName;
if(Arguments)
{
SIZE_T nArgumentsSize = ATL::GetTraceArgumentsSize(pszFormat, Arguments);
ATLASSERT(!IsBadReadPtr(Arguments, nArgumentsSize));
ATLASSERT(nArgumentsSize <= sizeof ArgumentsEx.pnBuffer); CopyMemory(ArgumentsEx.pnBuffer, Arguments, nArgumentsSize); } AtlTraceVA(m_nModule, pszFileName ? strrchr(pszFileName, '\\') + 1 : pszFileName, nLine, nCategory, nLevel, pszFormatEx, (va_list) &ArgumentsEx); #else pszFunctionName; AtlTraceVA(m_nModule, pszFileName, nLine, nCategory, nLevel, pszFormat, Arguments); #endif // defined(_DEBUG) }

To print function names in front of debug output we are inserting “%hs: “ format before the provided pszFormat and we also need to insert an LPCSTR pointer into the beginning of variable argument list Arguments. We need to find list size, for simplicity (unexact) it can be obtained using the following function GetTraceArgumentsSize:

inline SIZE_T GetTraceArgumentsSize(LPCSTR pszFormat, va_list Arguments)
{
SIZE_T nSize = 8 * sizeof (BYTE*);
if(pszFormat)
for(LPCSTR pszPointer = pszFormat; ; )
{
LPCSTR pszOccurrence = strchr(pszPointer, ‘%’);
if(!pszOccurrence)
break;
nSize += sizeof (BYTE*);
pszPointer = pszOccurrence + 1;
}
return nSize;
}

In order to not flood output with file paths, strrchr is used to strip source file directory and print file name only. It does not appear to be a problem with Visual Studio .NET IDE to remain lines clickable and navigating to source code position.

Update complete? Yes:

application.h(424) : atlTraceGeneral: CApplication::Start: …
aiatlcom.h(651) : atlTraceGeneral: CThreadT::Initialize: Thread 4828 (0x12dc) has been started from thread 2984
aiatlcom.h(588) : atlTraceGeneral: CThreadT::ThreadProc: Thread 4828 (0x12dc) has been started

Function names are in debug output now. Very convenient!

Categories: ATL

Leave a Reply