In all the drivers and applications we develop here at GuruCE, we always take the time to add plenty of logging because we know good logging will save us loads of time debugging our code (not that we ever have to debug code here of course... ;)).
Windows CE has a really great mechanism for debug logging called Debug Zones. This mechanism allows you to divide your debug messages into 16 zones and each of these zones can be turned on or off at run-time. This allows you to control the verbosity of your debug output and helps prevent cluttering of messages. Read this excellent blog post by Travis first before reading on: http://blogs.msdn.com/ce_base/archive/2006/12/18/debug-messages-and-debu...
To add debug logging using Debug Zones to your application or driver do the following:
1. Define your zones using the DEBUGZONE macro
1. Define a DBGPARAM structure and fill it
2. Call either DEBUGREGISTER or RETAILREGISTERZONES from your DllMain (driver) or WinMain (application)
3. Log messages using either DEBUGMSG or RETAILMSG
Sounds easy (and actually it is) but to make it more easy (and because we found ourselves doing the same thing over and over) we created a template we now use in all our driver and application code, even on projects targeted for Desktop Windows. Even though "Big" Windows doesn't have a "Debug Zone" mechanism you can still use the template. The big benefit of course being that your code is completely portable between desktop PC's and CE devices (and also MBS/UNICODE)! The only thing you can't do is switch debug zones at run-time, but you can still select which zones should be on or off at compile-time.
We have released these templates under GPL for the greater good.
Let us explain the template by walking through it; we'll start with DebugZones.cpp (the line numbers reflect the actual line numbers in the template files):
-
#include <windows.h>
-
#include "DebugZones.h"
-
-
// Do NOT change anything in this file.
-
// See DebugZones.h for things you can change!
-
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
-
extern "C" DBGPARAM dpCurSettings =
-
{
-
__WMODULE__,
-
{
-
ZONE0_TEXT, ZONE1_TEXT, ZONE2_TEXT, ZONE3_TEXT,
-
ZONE4_TEXT, ZONE5_TEXT, ZONE6_TEXT, ZONE7_TEXT,
-
ZONE8_TEXT, ZONE9_TEXT, ZONE10_TEXT, ZONE11_TEXT,
-
ZONE12_TEXT, ZONE13_TEXT, ZONE14_TEXT, ZONE15_TEXT
-
},
-
#ifndef DEBUG // IN RETAIL...
-
RETAILZONES
-
#else //IN DEBUG...
-
DEBUGZONES
-
#endif
-
};
The first and most important thing to note is that you should not change anything in this file. You configure the template all through the header file, which we'll will be discussing later.
In this first part of DebugZones.cpp we simply define a global variable dpCurSettings of type DBGPARAM and fill the structure with defines from DebugZones.h. The __WMODULE__ corresponds to the "lpszName" member, the ZONEx_TEXT defines fill the "rglpszZones" array and finally RETAILZONES or DEBUGZONES (depending on whether you build a "debug" or "release" version of the code) corresponds to the "ulZoneMask" member of the DBGPARAM structure. The CE debug system uses this structure to get Debug Zone information from the module and to set or clear debug zones during execution of the module. When you use the template for code targeted at Desktop Windows this structure will only be used by the DEBUGMSG/RETAILMSG macros (they use the ulZoneMask member as a display condition).
-
#ifndef UNDER_CE
-
#include <stdio.h>
-
void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...)
-
{
-
#define BUFFER_SIZE 1000
-
va_list argptr;
-
va_start(argptr, lpszFmt);
-
TCHAR szBuffer[BUFFER_SIZE];
-
_vstprintf_s(szBuffer, BUFFER_SIZE, lpszFmt, argptr);
-
OutputDebugString(szBuffer);
-
va_end(argptr);
-
}
-
#endif
This code is only used when you target Desktop Windows. Only Windows CE defines NKDbgPrintf so for Desktop Windows we had to create our own implementation of this function to make the Debug Zones system portable. This function simply takes a format string followed by a variable number of parameters and transforms that into a formatted string and outputs it to the debug output window of Visual Studio. Note that the maximum length of the formatted string is BUFFER_SIZE characters.
-
// Helper function for runtime conversion of char* to _T
-
LPCTSTR to_T(LPCSTR pStr)
-
{
-
#define MAX_CALLS 4
-
#define MAX_MSG 500
-
-
static TCHAR buffer[MAX_CALLS][MAX_MSG];
-
static DWORD index = 0;
-
LPCTSTR pszRet = buffer[index];
-
size_t conv;
-
-
#ifdef UNICODE
-
mbstowcs_s(&conv, buffer[index], MAX_MSG, pStr, MAX_MSG);
-
#else //ANSI
-
strcpy_s(buffer[index], MAX_MSG, pStr);
-
#endif
-
-
index++;
-
if (MAX_CALLS==index)
-
index = 0;
-
return pszRet;
-
}
This next bit of code is a helper function that solves a problem when you want your code to be portable between MBS and UNICODE. MBS is multi-byte string, also called ANSI string, and UNICODE is, well, UNICODE strings. Let's say you are compiling for UNICODE but want to display a multi-byte string using a function that uses a format string. The way to do that is to use %S instead of %s in the format string. When you use %s in code compiled for UNICODE the variable indicated by %s should be in UNICODE. When you compile for MBS the variable indicated by %s should be MBS. In other words, %s indicates the "native" string format. If you want to display a multi-byte string in code compiled for UNICODE you'd use %S, and if you want to display a UNICODE string in code compiled for MBS you'd use %S as well. In other words, %S indicates the "opposite" string format. And here lies the problem: What if you want to display a multi-byte string regardless of whether the code is compiled for MBS or UNICODE?
Take a look at the following code:
NKDbgPrintf(_T("Display a native format string \"%s\"\r\n"), _T("This is a native string format"));
NKDbgPrintf(_T("Display opposite string format \"%S\"\r\n"), "This is a multi-byte string");
Note that the _T macro is defined as L in UNICODE (making the following string a UNICODE string) and as nothing in MBS (leaving the following string a multi-byte string).
When compiled for UNICODE the above code works perfectly:
Display a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"
But now see what happens when you compile the above code for MBS:
Display a native format string "This is a native string format"
You only get the output of the first line! So what just happened? Well, the _vstprintf_s function in NKDbgPrintf expected the first argument to be of the opposite string format. Since you compiled for MBS this means it expects a UNICODE string. The _vstprintf_s function detects it is not and bails out, setting szBuffer to an empty string.
The to_T function solves this problem:
NKDbgPrintf(_T("Display a native format string \"%s\"\r\n"), _T("This is a native string format"));
NKDbgPrintf(_T("Display opposite string format \"%s\"\r\n"), to_T("This is a multi-byte string"));
When compiled for MBS the output is:
Display a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"
I can hear you say: So why don't you just use _T() in that 2nd call to NkDbgPrintf? And yes, you are right, in the above code that would work, but what if it's a variable pointing to a multi-byte string? You can't use _T() around a variable:
NKDbgPrintf(_T("IPAddress \"%s\"\r\n"), to_T(inet_ntoa(ipAddress)));
Line 67 defines the maximum number of times you can call this function before it will start overwriting its internal buffers. Increase this value if you want to use more than 4 parameters that need to be converted in one call. To understand this, see the following code and its output:
char
* s1
= "String 1";
char
* s2
= "String 2";
char
* s3
= "String 3";
char
* s4
= "String 4";
char
* s5
= "String 5";
char
* s6
= "String 6";
NKDbgPrintf(_T("%s %s %s %s %s %s\r\n"), to_T(s1), to_T(s2), to_T(s3), to_T(s4), to_T(s5), to_T(s6));
Output:
String 1 String 2 String 3 String 4 String 1 String 2
Let's move on to the real core of the template; DebugZones.h:
-
#pragma once
-
#include <windows.h>
-
-
// Change the following definitions:
-
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
-
#define __MODULE__ "MyDriver" // The module name
-
-
// Define this to issue a DEBUHCHK(FALSE) when printing an ERRMSG and/or WRNMSG
-
//#define DEBUGCHK_ON_ERRMSG
-
//#define DEBUGCHK_ON_WRNMSG
-
-
// Definitions for our debug zones
-
#define ZONE0_TEXT _T("Init")
-
#define ZONE1_TEXT _T("Function")
-
#define ZONE2_TEXT _T("Memory")
-
#define ZONE3_TEXT _T("")
-
#define ZONE4_TEXT _T("")
-
#define ZONE5_TEXT _T("")
-
#define ZONE6_TEXT _T("")
-
#define ZONE7_TEXT _T("")
-
#define ZONE8_TEXT _T("")
-
#define ZONE9_TEXT _T("")
-
#define ZONE10_TEXT _T("")
-
#define ZONE11_TEXT _T("")
-
#define ZONE12_TEXT _T("")
-
#define ZONE13_TEXT _T("Info")
-
#define ZONE14_TEXT _T("Warning")
-
#define ZONE15_TEXT _T("Error")
-
-
// These macros can be used as condition flags for LOGMSG
-
#define ZONE_INIT DEBUGZONE(0)
-
#define ZONE_FUNCTION DEBUGZONE(1)
-
#define ZONE_MEMORY DEBUGZONE(2)
-
#define ZONE_3 DEBUGZONE(3)
-
#define ZONE_4 DEBUGZONE(4)
-
#define ZONE_5 DEBUGZONE(5)
-
#define ZONE_6 DEBUGZONE(6)
-
#define ZONE_7 DEBUGZONE(7)
-
#define ZONE_8 DEBUGZONE(8)
-
#define ZONE_9 DEBUGZONE(9)
-
#define ZONE_10 DEBUGZONE(10)
-
#define ZONE_11 DEBUGZONE(11)
-
#define ZONE_12 DEBUGZONE(12)
-
#define ZONE_INFO DEBUGZONE(13)
-
#define ZONE_WARNING DEBUGZONE(14)
-
#define ZONE_ERROR DEBUGZONE(15)
-
-
// These macros can be used to indicate which zones to enable by default (see RETAILZONES and DEBUGZONES below)
-
#define ZONEMASK_INIT ZONEMASK(0)
-
#define ZONEMASK_FUNCTION ZONEMASK(1)
-
#define ZONEMASK_MEMORY ZONEMASK(2)
-
#define ZONEMASK_3 ZONEMASK(3)
-
#define ZONEMASK_4 ZONEMASK(4)
-
#define ZONEMASK_5 ZONEMASK(5)
-
#define ZONEMASK_6 ZONEMASK(6)
-
#define ZONEMASK_7 ZONEMASK(7)
-
#define ZONEMASK_8 ZONEMASK(8)
-
#define ZONEMASK_9 ZONEMASK(9)
-
#define ZONEMASK_10 ZONEMASK(10)
-
#define ZONEMASK_11 ZONEMASK(11)
-
#define ZONEMASK_12 ZONEMASK(12)
-
#define ZONEMASK_INFO ZONEMASK(13)
-
#define ZONEMASK_WARNING ZONEMASK(14)
-
#define ZONEMASK_ERROR ZONEMASK(15)
-
-
#define RETAILZONES ZONEMASK_INIT | ZONEMASK_WARNING | ZONEMASK_ERROR
-
#define DEBUGZONES RETAILZONES | ZONEMASK_FUNCTION | ZONEMASK_MEMORY | ZONEMASK_INFO
-
-
// Define FULL_CONTEXT to show full context in debug messages:
-
// "Module: [full path to file name:line number] Function>"
-
//#define FULL_CONTEXT
-
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
This is the part of the template you should change to match your driver or application. First, in line 33, change the __MODULE__ define to the name of your driver or application. If you are creating a flash driver you'd set this to "FLASH" for instance.
Uncomment line 36 and/or 37 to generate a DEBUGCHK whenever you use ERRMSG and/or WRNMSG. You'd usually only uncomment these lines when you are in the development stage (when you're still actively debugging your code).
Next, in lines 40 to 55, you define the friendly names of the debug zones you want. These names will show up when you use the Debug Zones dialog or the "zo" command from Platform Builder. The predefined ones are always useful:
- Init
Use this zone to display debug messages during the initialization phase of your driver. I use this zone in the Init function of drivers.
- Function
This zone is used to display function entry and exit. Used by the FUNC_ENTRY and FUNC_EXIT macros (I'll discuss those later)
- Memory
This zone is used to display memory allocation and de-allocation. Having a separate zone for memory really helps in identifying memory leaks or memory related problems in your driver or application (besides CeDebugX)
- Info
This zone is used to display general information. I use this zone a lot through-out my code and enabling this zone usually means I get heaps of debug messages, but also heaps of information about the flow of my code
- Warning
Use this zone for non-fatal errors (also known as warnings ;o)
- Error
Use this zone for fatal errors; situations where you had to abort a certain operation
Another zone I use often is "IOCTL". If my driver handles custom IOCTLs I use that zone to display information when those IOCTLs are handled. If you want to add the IOCTL zone you'd change line 43 to:
-
#define ZONE3_TEXT _T("IOCTL")
Lines 58 to 73 define the actual zone IDs. You use these defines as the first parameter of LOG_MSG (that I'll discuss later) to indicate the zone this message belongs to. After you added the IOCTL zone to the friendly names list, you'd change line 61 to:
-
#define ZONE_IOCTL DEBUGZONE(3)
Lines 76 to 91 define the zone masks. You use these defines to indicate which zones should be enabled by default. After you added the IOCTL zone to the zone ID list, you'd change line 79 to:
-
#define ZONEMASK_IOCTL ZONEMASK(3)
Line 93 and 94 define the default zones to use in a release build and a debug build. I enable zones INIT, WARNING and ERROR in a release build and add FUNCTION, MEMORY and INFO to debug builds. Note that in a "ship" build (on Windows CE, WINCESHIP=1) debug/retail messages are defined as "nothing" (so all the logging code is effectively removed from the final binary).
If you define FULL_CONTEXT (line 98) the LOGMSG macro will output the full path to the source code filename in front of the message, instead of just the module name. We don't enable this by default because it makes the debug output cluttered and less readable (but it can be very handy in some cases).
This concludes the part of DebugZones.h that you should change. The remainder of the code mostly deals with the differences between Desktop Windows and Windows CE:
-
// Do NOT change any of the following definitions:
-
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
-
LPCTSTR to_T(LPCSTR pStr);
-
-
#ifdef UNDER_CE
-
#ifndef DEBUG
-
#ifdef __cplusplus
-
extern "C" DBGPARAM dpCurSettings;
-
#else //!__cplusplus
-
extern DBGPARAM dpCurSettings;
-
#endif __cplusplus
-
#endif DEBUG
-
#else // !UNDER_CE
-
#include <tchar.h>
-
#include <stdio.h>
-
-
typedef struct _DBGPARAM {
-
TCHAR lpszName[32]; // @field Name of module
-
TCHAR rglpszZones[16][32]; // @field names of zones for first 16 bits
-
ULONG ulZoneMask; // @field Current zone Mask
-
} DBGPARAM, *LPDBGPARAM;
-
-
#ifndef DEBUG
-
#ifdef _DEBUG
-
#define DEBUG
-
#endif _DEBUG
-
#endif DEBUG
-
-
extern DBGPARAM dpCurSettings;
-
void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...);
-
-
#define RETAILMSG(cond,printf_exp) \
-
((cond)?(NKDbgPrintf printf_exp),1:0)
-
-
#define DEBUGZONE(n) (dpCurSettings.ulZoneMask & (0x00000001<<(n)))
-
-
#define RETAILREGISTERZONES(hMod)
-
-
#ifdef DEBUG
-
#define DEBUGMSG(cond,printf_exp) \
-
((void)((cond)?(NKDbgPrintf printf_exp),1:0))
-
-
#define DBGCHK(module,exp) \
-
((void)((exp)?1:( \
-
NKDbgPrintf ( _T("%s: DEBUGCHK failed in file %s at line %d \r\n"), \
-
module, _T(__FILE__) ,__LINE__ ), \
-
DebugBreak(), \
-
0 \
-
)))
-
-
#define DEBUGCHK(exp) DBGCHK(dpCurSettings.lpszName, exp)
-
#define DEBUGREGISTER(hMod)
-
#endif DEBUG
-
#endif
-
-
#ifndef __FUNCTION__
-
#define __FUNCTION__ __FILE__
-
#endif __FUNCTION__
-
-
#ifdef UNICODE
-
#define PREPW(x) L ## x
-
#else //!UNICODE
-
#define PREPW(x) x
-
#endif UNICODE
-
-
#define TOWSTR(x) PREPW(x)
-
#ifndef __WFUNCTION__
-
#define __WFUNCTION__ TOWSTR(__FUNCTION__)
-
#endif __WFUNCTION__
-
#define __WMODULE__ TOWSTR(__MODULE__)
-
#ifndef __WFILE__
-
#define __WFILE__ TOWSTR(__FILE__)
-
#endif __WFILE__
-
-
#define ZONEMASK(n) (0x00000001<<(n))
-
-
#ifdef FULL_CONTEXT
-
#define FUNC_ENTRY(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": +[") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("(") fmt _T(")+\r\n"), __LINE__, __VA_ARGS__))
-
#define FUNC_EXIT(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": -[") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("(") fmt _T(")-\r\n"), __LINE__, __VA_ARGS__))
-
#define LOGMSG(cond, fmt, ...) RETAILMSG(cond, (__WMODULE__ _T(": [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ") fmt, __LINE__, __VA_ARGS__))
-
#ifdef DEBUGCHK_ON_ERRMSG
-
#define WRNMSG(fmt, ...) {RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(": [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> WARNING: ") fmt, __LINE__, __VA_ARGS__)); DEBUGCHK(FALSE);}
-
#define ERRMSG(fmt, ...) {RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(": [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ERROR: ") fmt, __LINE__, __VA_ARGS__)); DEBUGCHK(FALSE);}
-
#else // !DEBUGCHK_ON_ERRMSG
-
#define WRNMSG(fmt, ...) RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(": [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> WARNING: ") fmt, __LINE__, __VA_ARGS__))
-
#define ERRMSG(fmt, ...) RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(": [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ERROR: ") fmt, __LINE__, __VA_ARGS__))
-
#endif
-
#else
-
#define FUNC_ENTRY(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": +") __WFUNCTION__ _T("(") fmt _T(")+\r\n"), __VA_ARGS__))
-
#define FUNC_EXIT(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": -") __WFUNCTION__ _T("(") fmt _T(")-\r\n"), __VA_ARGS__))
-
#define LOGMSG(cond, fmt, ...) RETAILMSG(cond, (__WMODULE__ _T(": ") __WFUNCTION__ _T("> ") fmt, __VA_ARGS__))
-
#ifdef DEBUGCHK_ON_ERRMSG
-
#define WRNMSG(fmt, ...) {RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(": ") __WFUNCTION__ _T("> WARNING: ") fmt, __VA_ARGS__)); DEBUGCHK(FALSE);}
-
#define ERRMSG(fmt, ...) {RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(": ") __WFUNCTION__ _T("> ERROR: ") fmt, __VA_ARGS__)); DEBUGCHK(FALSE);}
-
#else // !DEBUGCHK_ON_ERRMSG
-
#define WRNMSG(fmt, ...) RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(": ") __WFUNCTION__ _T("> WARNING: ") fmt, __VA_ARGS__))
-
#define ERRMSG(fmt, ...) RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(": ") __WFUNCTION__ _T("> ERROR: ") fmt, __VA_ARGS__))
-
#endif
-
#endif
-
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
The interesting part is from line 190 to 199 where we define a couple of macros that allow you to log messages in a good and consistent format:
- FUNC_ENTRY
Usage:
FUNC_ENTRY(format_string [, arg1, arg2, ...]);
Examples:
FUNC_ENTRY(_T("void"));
FUNC_ENTRY(_T("lpParameter = 0x%08X"), lpParameter);
Output:
(Note the '+' characters that indicate function entry)
ModuleName: +FunctionName(void)+
ModuleName: +FunctionName(lpParameter = 0xA5A5A5A5)+
- FUNC_EXIT
Usage:
FUNC_EXIT(format_string [, arg1, arg2, ...]);
Examples:
FUNC_EXIT(_T(""));
FUNC_EXIT(_T("return %s"), bRet ? _T("true") : _T("false"));
Output:
(Note the '-' characters that indicate function exit)
ModuleName: -FunctionName()-
ModuleName: -FunctionName(return false)-
- LOGMSG
Usage:
LOGMSG(ZONE_ID, format_string [, arg1, arg2, ...]);
Examples:
LOGMSG(ZONE_INIT, _T("Started successfully!\r\n"));
LOGMSG(ZONE_INFO, _T("dwID = %d\r\n"), dwID);
Output:
ModuleName: FunctionName> Started successfully!
ModuleName: FunctionName> dwID = 1234
- ERRMSG
Examples:
ERRMSG(_T("Invalid parameter (dwParam = %d)\r\n"), dwParam);
Output:
ModuleName: FunctionName> ERROR: Invalid parameter (dwParam = 1234)
- WRNMSG
Examples:
WRNMSG(_T("Could not reach IP %s\r\n"), to_T(inet_ntoa(IPAddress)));
Output:
ModuleName: FunctionName> WARNING: Could not reach IP 10.0.0.1
To use the Debug Zones template in your projects you simply add DebugZones.cpp and DebugZones.h to your solution and call RETAILREGISTERZONES(hMod) from your entry point function:
BOOL WINAPI DriverEntry
(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved
)
{
FUNC_ENTRY
(_T
("hInstance = 0x%08X, dwReason = 0x%08X"), hInstance, dwReason
);
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
// Disable thread creation/deletion calls into this function
DisableThreadLibraryCalls((HMODULE)hInstance);
// Register with debug subsystem
RETAILREGISTERZONES((HMODULE)hInstance);
break;
}
case DLL_PROCESS_DETACH:
{
break;
}
default:
break;
}
FUNC_EXIT(_T("TRUE"));
return TRUE;
}
or in case of an application:
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hInstPrev,
LPWSTR pCmdLine,
int nCmdShow)
{
// Register with debug subsystem
RETAILREGISTERZONES(NULL);
.
.
.
Note that the parameter to RETAIL/DEBUGREGISTERZONES MUST be NULL if you want to register debug zones from an application!
If you are using precompiled headers you have to set DebugZones.cpp to "Not Using Precompiled Headers" (in VS2008 right click DebugZones.cpp in your solution, click "Properties", then select "Precompiled Headers" under "Configuration Properties -> C/C++" and change the "Create/Use Precompiled Header" value). Now include "DebugZones.h" in any file you want to use debug zone logging.
The attached ZIP file contains the complete template.
I hope this template will prove as useful to you as it is to us!