Debugging
OutputDebugString in MFC: Debug Messages That Still Work in a Release Build
An order import works on your PC but stops partway on a tester’s PC, where only the Release build is installed. During development you added TRACE calls to the import, so you start DebugView on that PC and run the import again. DebugView stays empty.
TRACE generates no code in a Release build, so there was nothing to receive. For messages that a Release build still sends, call the Win32 function OutputDebugString yourself. DebugView receives them without Visual Studio and without a debugger.
TRACE is removed from a Release build, arguments included
In the Debug version of MFC, TRACE sends a formatted string to the debugger. In a Release build the macro compiles to nothing, and no code is generated (TRACE). That applies to the arguments too:
TRACE(_T("TRACE: %d rows in the import file\n"), CountRows());
In the Debug build, CountRows() runs once, and a tool such as DebugView receives this line. TRACE puts the source file, the line number, and the trace category in front of your text:
C:\MFCGuideWork\outputdebugstring-release-logging-mfc\ImportDlg.cpp(78) : atlTraceGeneral - TRACE: 3 rows in the import file
In the Release build, CountRows() is never called and nothing is sent. If a function inside TRACE changes something, such as a counter or a cached value, the two builds behave differently. Call it on its own line and pass the result to TRACE.
A logging helper built on OutputDebugString
Unlike TRACE, a call to OutputDebugString stays in the Release build. It takes a finished string and has no format arguments. This helper formats the text with CString::FormatV, adds a prefix, and ends the line:
// DebugLog.h
extern bool g_bDebugLog;
void DebugLog(LPCTSTR pszFormat, ...);
// DebugLog.cpp
#include "pch.h"
#include "DebugLog.h"
bool g_bDebugLog = true;
void DebugLog(LPCTSTR pszFormat, ...)
{
if (!g_bDebugLog)
return;
va_list args;
va_start(args, pszFormat);
CString sMessage;
sMessage.FormatV(pszFormat, args);
va_end(args);
::OutputDebugString(_T("[OrderImport] ") + sMessage + _T("\n"));
}
You call it with the same format string you would give TRACE, without the \n:
DebugLog(_T("Order %d read from %s, total %.2f"), order.nId, order.pszFile, order.dTotal);
- The prefix. DebugView shows output from every program that calls
OutputDebugString, not only yours. A fixed prefix lets you find your lines among them and filter on it. - The newline.
OutputDebugStringsends exactly the text you pass, and each call is one message.OutputDebugString(_T("Saving... "))followed byOutputDebugString(_T("done\n"))arrives as two messages, the first one without a line end. Adding\nin the helper keeps every message on its own line. - The switch. When
g_bDebugLogisfalse, the function returns before it formats anything. Microsoft recommends sending very little debug output and giving the user a way to turn it on and off (OutputDebugString). Set the flag from a setting or a command-line switch when the app starts.
Read the messages with DebugView
DebugView from Sysinternals captures OutputDebugString from Win32 programs. It is a single file, and capturing this output does not need administrator rights. Copy Dbgview.exe to the tester’s PC, start it, and then start your app outside Visual Studio. For the import above, the Release build sends these messages:
[OrderImport] Import started: 3 orders
[OrderImport] Order 1001 read from C:\Imports\Zürich\orders.csv, total 129.90
[OrderImport] Order 1002 read from C:\Imports\Lódz\orders.csv, total 58.00
[OrderImport] Order 1003 read from C:\Imports\??\orders.csv, total 240.50
[OrderImport] Saving...
done
[OrderImport] Import finished
The TRACE line is missing because this is the Release build. DebugView also shows the process ID of each message, which helps when two copies of the app are running.
Characters outside the system code page arrive as ? or a plain letter
The real folders are C:\Imports\Łódź and C:\Imports\東京, and the app opens them without trouble. In the messages, Łódź became Lódz and 東京 became ??.
In a Unicode MFC project, OutputDebugString is OutputDebugStringW. It converts the string with the system’s code page and passes the result to OutputDebugStringA, and DebugView receives that converted text (OutputDebugString). A debugger can ask Windows for the original Unicode string, so the same message may look correct in a debugger and wrong in DebugView. On a PC whose system code page is 1252 (English and Western European Windows), ü and ó survive, Ł and ź are replaced by the closest Latin letter, and characters with no near equivalent, such as Japanese, become ?. The path in the message then names a folder that does not exist.
The app’s own strings are not changed; only the copy in the message is. If you need to match a message to a file, put something in the message that survives the conversion, such as the order ID, and look up the full path in your own data.
Keep passwords and personal data out of the messages
Other programs on the PC can read these messages, and DebugView does not need administrator rights to do it. The Release build is the one your users run, so do not pass passwords, access tokens, or personal data to OutputDebugString.