MACRO Revisited

Macro is powerful, but few people understand how it works. In theory, syntax highlighting for C/C++ is impossible due to the presence of Preprocessing Directives FDIS N3290 16 . Sometimes I do feel that C++ is a mixture of three languages instead of a single language, I have to keep in mind that there are several Phases of Translation FDIS N3290 2.2 [lex.phases] when I was coding.


It turns out that most people who have been using the Win32 API and C Runtime Library for years don't know NULL is complicated than it looks. It is defined by both Windows headers and C Runtime headers, and guarded by macro. The reason behind this is to make most people happy (e.g. C++ standard requires NULL to be 0, while Standard C does not).

/* WinDef.h */
#ifndef NULL
#ifdef __cplusplus
#define NULL  0
#define NULL  ((void *)0)


You probably have noticed that I've used this macro extensively in my blogs, here goes the official voice:

WIN32_LEAN_AND_MEAN excludes APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.

So, if you don't need these APIs, WIN32_LEAN_AND_MEAN would make the life of compiler easier, plus, Precompiled Header, Intellisense and other code analysis tools would also benifit from it.


UNICODE is used by Windows header files to support generic Conventions for Function Prototypes and Generic Data Types.

_UNICODE is used by the C Runtime (CRT) header files to support Generic-Text Mappings.

The following interesting snippet is distilled from ATL headers:

/* atldef.h */
#ifdef  _UNICODE
#ifndef UNICODE
#define UNICODE         // UNICODE is used by Windows headers
#ifdef  UNICODE
#ifndef _UNICODE
#define _UNICODE        // _UNICODE is used by C-runtime/MFC headers

TEXT __TEXT and _T _TEXT __T

The following snippet is distilled from WinNT.h, which can be found from DDK/WDK and SDK/PSDK:

/* WinNT.h */
#ifdef  UNICODE
#define __TEXT(quote) L##quote
#define __TEXT(quote) quote
#define TEXT(quote) __TEXT(quote)

So the following code is correct:

_tprintf(TEXT("%s") TEXT("\n"), TEXT(__FILE__));

But this is wrong:

_tprintf(TEXT("%s" "\n"), __TEXT(__FILE__));

And if UNICODE is defined, it turns out that you can (evilly) use:

class LOST
TEXT(OST) lost;

The following snippet was distilled from tchar.h, which is a part of CRT:

/* tchar.h */
#ifdef  _UNICODE
#define __T(x)      x
#define __T(x)      L ## x
#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)


  1. Use TEXT if you are using none of the ATL, CRT and MFC.
  2. Use _T if you are using the ATL, CRT and MFC.
  3. Use _TEXT instead of _T if you are not as lazy as me.
  4. Don't use __T and __TEXT unless you have a special reason.


NDEBUG is a part of the C Language Standard, which controls the behavior of assert:

/* assert.h */
#ifdef NDEBUG
#define assert(_Expression) ((void)0)

_DEBUG is defined by the Microsoft C++ Compiler when you compile with /LDd, /MDd and /MTd. The runtime libraries such like ATL, CRT and MFC make use of this macro.

DEBUG is defined in ATL:

/* atldef.h */
#ifdef _DEBUG
#ifndef DEBUG
#define DEBUG


WINVER has been existing since 16bit Windows, and is still in using. Note that Windows NT 4.0 and Windows 95 both have WINVER defined as 0x0400.

_WIN32_WINDOWS is used by Windows 95/98/Me.

_WIN32_WINNT is used by the whole NT family.

NTDDI_VERSION was introduced by Windows 2000, as Win9x and NT evolved into a single operating system. Plus, NTDDI_VERSION contains more information and is able to distinguish service packs. The latest sdkddkver.h has all the information you would want to know.

_WIN32_IE was introduced because Internet Explorer shares many components with the shell (a.k.a. Windows Explorer), installing a new version of Internet Explorer would eventually replace a number of system components and even change the APIs.

VER_PRODUCTVERSION_W can be found in ntverp.h, which is used by the NT team to maintain the product build.


  1. Use NTDDI_VERSION whenever possible.
  2. Don't use WINVER unless you have special reason.
  3. Forget about _WIN32_WINDOWS unless you are still targeting Win9x or Win32s.
  4. Don't use VER_PRODUCTVERSION_W unless you are writing low level code such like drivers and debugger extensions.

_X86_ _AMD64_ _IA64_ and _M_AMD64 _M_IX86 _M_IA64 _M_X64

_M_AMD64, _M_IX86, _M_IA64 and _M_X64 are defined by the Microsoft C++ Compiler according to the target processor architecture. _M_AMD64 and _M_X64 are equivalent.

_X86_, _AMD64_ and _IA64_ are defined by Windows.h (there is no _X64_ at all, because AMD invented x86-64).

/* Windows.h */
#if !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86)
#define _X86_
#if !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64)
#define _AMD64_


If bitness matters, but we don't care about architecture, we can use _WIN32 and _WIN64 provided by the Microsoft C++ Compiler. This is useful while defining data types and function prototypes. Note that _WIN32 and _WIN64 are not mutual exclusive, as _WIN32 is always defined (unless you are using DDK and writing 16bit code).

WIN32 is defined by Windows header file WinDef.h, and is not widely used in Windows header files (TAPI being a negative example).

_WINDOWS is a legacy thing in the 16bit era, you should hardly see it in 21st century.

/* WinDef.h */
// Win32 defines _WIN32 automatically,
// but Macintosh doesn't, so if we are using
// Win32 Functions, we must do it here
#ifdef _MAC
#ifndef _WIN32
#define _WIN32
#endif //_MAC
#ifndef WIN32
#define WIN32



/* WinNT.h */
// Macros used to eliminate compiler warning generated when formal // parameters or local variables are not declared. // // Use DBG_UNREFERENCED_PARAMETER() when a parameter is not yet // referenced but will be once the module is completely developed. // // Use DBG_UNREFERENCED_LOCAL_VARIABLE() when a local variable is not yet // referenced but will be once the module is completely developed. // // Use UNREFERENCED_PARAMETER() if a parameter will never be referenced. // // DBG_UNREFERENCED_PARAMETER and DBG_UNREFERENCED_LOCAL_VARIABLE will // eventually be made into a null macro to help determine whether there // is unfinished work. //

(to be continued...)

Comments (2)
  1. DemosDemon says:

    Thank you for this… this answered some questions I had about the WIN32 api and all those preprocessor defines.

  2. Nsr8362 says:

    Thanks. The document is easy to read and understand.

Comments are closed.

Skip to main content