原文:C/C++ 中宏与预处理使用方法大全 (VC)
作者:Breaker <breaker.zy_AT_gmail>
C/C++ 中的宏 (#define) 与预处理 (#if/#ifdef/#pragma) 的使用方法大全、使用技巧
开发环境:VC 2005
关键字:宏, 预定义宏, 预处理, 预编译头, VC, #pragma, 编译选项, 程序区段
RTFM: Read The F__king Manual/MSDN
VC 中的宏使用方法参考 MSDN:
Macros (C/C++)
C/C++ 预定义宏^
__LINE__: 当前源文件的行号,整数
__FILE__: 当前源文件名,char 字符串,使用 /FC 选项产生全路径
__DATE__: 当前编译日期,char 字符串,格式 Aug 28 2011
__TIME__: 当前编译时间,char 字符串,格式 06:43:59
__STDC__: 整数 1,表示兼容 ANSI/ISO C 标准,配合 #if 使用
__TIMESTAMP__: 最后一次修改当前文件的时间戳,char 字符串,格式 Sun Aug 28 06:43:57 2011
__cplusplus: 以 C++ 方式而非 C 语言方式编译时定义,VC 2005 中定义为 199711L,配合 #ifdef 使用
例子:C/C++ 预定义宏的取值^
#if __STDC__
pszstdc = _T("YES");
#else
pszstdc = _T("NO");
#endif
#ifdef __cplusplus
pszcpp = _T("YES");
#else
pszcpp = _T("NO");
#endif
_tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"),
_T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp);
}
// 宏化的 PrintSourceInfo()
#define PRINT_SOURCE_INFO() \
_tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \
_T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__));
MacroTest.h 中定义函数 PrintSourceInfo() 和 PRINT_SOURCE_INFO(),在 MacroTest.cpp include=> MacroTest.h,并调用它们
输出结果:
(1). 使用函数 PrintSourceInfo(),无论 Debug/Release 方式编译,无论是否 inline 化 PrintSourceInfo(),输出结果相同,均是 MacroTest.h 的信息:
File: d:\source\macrotest\macrotest.h, Line: 64, Date: Aug 28 2011, Time: 06:43:59, Timestamp: Sun Aug 28 06:43:57 2011, ANSI/ISO C: NO, C++: YES
(2). 使用宏 PRINT_SOURCE_INFO(),Debug/Release 方式编译输出结果大致相同,均是 MacroTest.cpp 的信息,只是 Debug 输出的 __FILE__ 是全路径,而 Release 输出的是相对路径:
File: d:\source\macrotest\macrotest.cpp, Line: 14, Date: Aug 28 2011, Time: 07:42:30, Timestamp: Sun Aug 28 07:38:25 2011
说明:
(1). __FILE__、__DATE__、__TIME__ 是 char 字符串,而不是 wchar_t 宽字符字符串,需配合 _T()、_t 系列函数使用
(2). 如果在函数 PrintSourceInfo() 中使用宏,则 __FILE__、__LINE__、__TIME__ 等表示的是 PrintSourceInfo() 所在文件,即例 1 中的 MacroTest.h 的信息;如果在宏 PRINT_SOURCE_INFO() 中使用宏,因为宏 PRINT_SOURCE_INFO() 嵌套展开的缘故,__FILE__ 等表示的是 PRINT_SOURCE_INFO() 展开所在文件,即 MacroTest.cpp 的信息
(3). 无论使用 PrintSourceInfo() 还是 PRINT_SOURCE_INFO(),__LINE__ 总是文件 .h/.cpp 的固有行号,而非 [MacroTest.cpp include=> MacroTest.h] 预处理展开后的行号
(4). 在 VC 2005 中,上述编译方式下没有定义 __STDC__,要使 __STDC__ = 1,应同时满足以下条件:
- (a). 以 C 方式编译
- (b). 使用编译选项 /Za,表示禁止 Microsoft C/C++ 语言扩展,从而兼容 ANSI C/C++
C/C++ 预定义宏用途:诊断与调试输出^
参考 VC CRT 和 MFC 的代码,注意:需要在宏中使用 __FILE__、__LINE__,原因见上面“说明 (2)”
CRT 的诊断与调试输出:assert, _ASSERT/_ASSERTE, _RPTn/_RPTFn/_RPTWn/_RPTFWn^
CRT 的诊断宏 assert()、_ASSERT()/_ASSERTE()
_CRTIMP void __cdecl _wassert(__in_z const wchar_t * _Message, __in_z const wchar_t *_File, __in unsigned _Line);
#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
// crtdbg.h
#define _ASSERT_EXPR(expr, msg) \
(void) ((!!(expr)) || \
(1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \
(_CrtDbgBreak(), 0))
#ifndef _ASSERT
#define _ASSERT(expr) _ASSERT_EXPR((expr), NULL)
#endif
#ifndef _ASSERTE
#define _ASSERTE(expr) _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
#endif
CRT 的调试输出宏 _RPTn()/_RPTFn(),n: 0 ~ 5
_RPTWn()/_RPTFWn() 是宽字符版
#define _RPT_BASE(args) \
(void) ((1 != _CrtDbgReport args) || \
(_CrtDbgBreak(), 0))
#define _RPTF0(rptno, msg) \
_RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg))
MFC 的诊断与调试输出:ASSERT/VERIFY, ASSERT_VALID, TRACE/TRACEn^
MFC 的诊断宏 ASSERT()/VERIFY()、ASSERT_VALID()
#define ASSERT(f) DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))
#define ASSERT_VALID(pOb) DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))
MFC 的调试输出宏 TRACE()/TRACEn(),n: 0 ~ 3
#ifndef ATLTRACE
#define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
#define ATLTRACE2 ATLTRACE
#endif
// afx.h
#include <atltrace.h>
#define TRACE ATLTRACE
#define THIS_FILE __FILE__
#define VERIFY(f) ASSERT(f)
#define DEBUG_ONLY(f) (f)
#define TRACE0(sz) TRACE(_T("%s"), _T(sz))
#define TRACE1(sz, p1) TRACE(_T(sz), p1)
#define TRACE2(sz, p1, p2) TRACE(_T(sz), p1, p2)
#define TRACE3(sz, p1, p2, p3) TRACE(_T(sz), p1, p2, p3)
MFC 的调试版 new^
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
// 用户代码
// 调试版 new
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CRT 和 C 标准库中的宏^
VC CRT 和 C 标准库中的宏参考 MSDN:
Global Constants
NULL 空指针^
NULL 在 stddef.h, stdio.h, stdlib.h 等多个头文件中定义,是地址/指针类型的 0,如下:
C++ 中的 0 是类型自动的,所以用 0 定义 NULL;而 C 中 0 是确定的 int 类型,所以需要强制
C++ 中,当 NULL 的相关操作数,如:对比操作 ptr == NULL,或函数的形参是指针类型时,或者能够“从指针类型隐式转换”时,0 被自动转换为指针类型
例子:NULL 隐式转换和 0 是类型自动的^
bool IsNull() const
{
return (m_Ptr == NULL);
}
private:
void* m_Ptr;
};
// 形参可以从指针类型 void* 隐式转换
void TestPointer(Pointer ptr)
{
_tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT "));
}
// 用户代码
TestPointer(0); // OK,0 是类型自动的,0 被自动转换为 void*,再次隐式转换为 Pointer
TestPointer(NULL); // OK,NULL 就是 0,同上
TestPointer(1); // Error,C++ 中 1 不同于 0,它是确定的 int 类型,
// 只能提升转换到 float/double 类型,不能自动转换为指针
TestPointer((int*)1); // OK,强制转换 1 为 int*,int* 自动转换为 void*,再次隐式转换为 Pointer
// 注意:void* 到 int* 不能自动转换,需要强制,参考 malloc() 的返回值
limits.h 整数类型常量^
在 limits.h 中定义,定义了各种 int 类型 (unsigned, char, short, long, __int64) 的最小、最大值,如 SCHAR_MAX (signed char MAX)、UCHAR_MAX (unsigned char MAX)、USHRT_MAX (unsigned short MAX) 等。编译时,如果 int 字面量超出这些范围,会编译出错
参考 MSDN:
Integer Limits
float.h 浮点类型常量^
在 float.h 中定义,定义各种浮点类型 (float, double, long double) 的极限值,如最小、最大值,最小浮点差量 (epsilon) 等
参考 MSDN:
Floating Limits
例子:浮点数极限值:判断浮点数是否相等^
// 对比两个 double 是否相等
inline
bool double_equal(double l, double r)
{
return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON);
}
// 打印函数的结果
#define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))
// 用户代码
// 对比 double 是否为 0 时,double_equal0() 更精确
// 对比两个 double 是否相等时,最好用 double_equal()
TEST_BOOL_FUNC(double_equal0(0)); // TRUE
TEST_BOOL_FUNC(double_equal0(DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal0(DBL_MIN)); // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_MIN)); // FALSE
TEST_BOOL_FUNC(double_equal(0, 0)); // TRUE
TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0)); // FALSE
TEST_BOOL_FUNC(double_equal(DBL_MIN, 0)); // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN)); // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_MIN)); // TRUE
math.h 数学常量^
数学计算常用的浮点数常量,如 M_PI (pi), M_E (e), M_SQRT2 (sqrt(2)) 等。这些数学常量不是标准 C/C++ 的一部分,而是 Microsoft 的扩展,使用前需要定义 _USE_MATH_DEFINES:
EOF 常量^
EOF (end-of-file) 常量,定义为 (-1),有宽字符版 WEOF ((wint_t)(0xFFFF)),EOF 和 WEOF 在 stdio.h 中定义,还有 _TCHAR 版 _TEOF,在 tchar.h 中定义。EOF 在流、I/O 操作中表示到达流、文件末尾(EOF 条件),也用来表示发生错误情况
例子:标准输入的 EOF^
// 要用存储空间 >= _gettchar() 返回值类型的变量保存其返回值
// 而不要用 char ch = _getchar(),那样会截断其返回值类型
int ch;
while ((ch = _gettchar()) != _TEOF)
_tprintf(_T("[%c]"), (_TCHAR)ch);
_tprintf(_T("\nread stdin: %s\n"), (feof(stdin) ? _T("EOF") : _T("Error")));
测试输出,用 Ctrl + Z 产生 EOF 信号:
read stdin: EOF
errno.h 错误代码^
在 errno.h 中定义,是测试全局量 errno 的值,errno 在 VC 中实现为线程安全的函数,而非全局变量。错误代码以 E 打头如 EINVAL:不合法的参数错误
错误代码具体值参考 MSDN:
errno Constants 和
errno, _doserrno, _sys_errlist, and _sys_nerr
locale 类别^
locale 类别 (Categories),在 locale.h 中定义,如 LC_ALL、LC_CTYPE
_MAX_PATH 等文件名与路径长度限制^
包括全路径与各部分路径的限制,即 FILENAME_MAX、_MAX_PATH、_MAX_DRIVE、_MAX_EXT、_MAX_FNAME、_MAX_DIR,在 stdlib.h 中定义。最大全路径长度限制在 260,和 Windows 的 MAX_PATH 相同,这是为了兼容 Windows 98 FAT32 文件系统。CRT 支持 32767 长度的文件名,方法和 Windows API 相同,即使用 "\\?\" 路径前缀,并调用 Unicode 宽字符版的 CRT 函数
RAND_MAX 随机数最大值^
在 stdlib.h 中定义为 32767,rand() 函数会产生 0 ~ RAND_MAX 之间的伪随机 int 值
例子:用 RAND_MAX 产生某个范围内的随机数^
if (seed) // Release 方式编译时,这个判断语句会被优化掉
srand((unsigned int) time(NULL));
return (Type) (((double) rand() / (double) RAND_MAX) * (max - min) + min);
}
template<typename Type>
inline
Type get_rand_seed(Type min, Type max)
{
return get_rand<true>(min, max);
}
template<typename Type>
inline
Type get_rand_noseed(Type min, Type max)
{
return get_rand<false>(min, max);
}
// 用户代码
#define RANGE_MIN 10
#define RANGE_MAX 100
int randnum;
randnum = get_rand_seed(RANGE_MIN, RANGE_MAX);
randnum = get_rand_noseed(RANGE_MIN, RANGE_MAX);
va_arg/va_start/va_end 访问变长函数参数^
用于访问类似 printf(const char* format, ...) 等变长函数参数的辅助宏,在 stdarg.h 中声明,参考 MSDN:
va_arg, va_end, va_start
宏实现的 CRT 函数^
在 VC CRT 中有些函数以宏和函数两种方式实现,如 getchar(),并优先使用宏版本,
强制使用函数版的方法:
(1). 调用时给函数名加括号,如 (getchar)()
(2). 调用前,取消宏版本的定义,如 #undef getchar
两种实现方式的比较见 MSDN:
Recommendations for Choosing Between Functions and Macros
Microsoft 预定义宏^
VC C/C++ 和 Microsoft 预定义宏参考 MSDN:
Predefined Macros
这些宏可以分类如下:
平台与系统类^
_M_IX86: IA32/x86 平台
_M_IA64: IA64/IPF (Itanium Processor Family) 64bit 平台
_M_X64: x64/x86-64/AMD64 平台
WIN32, _WIN32: Win32 和 Win64 程序开发都会定义
_WIN64: Win64 程序开发
_CONSOLE: 控制台 Windows 程序开发,链接 Console 子系统:/SUBSYSTEM:CONSOLE
_WINDOWS: 非控制台 Windows 程序开发,链接 Windows 子系统:/SUBSYSTEM:WINDOWS
版本号类^
通常定义为数字,配合 #if (XXX >= 1000) 使用,启动、禁用特定部分的代码、特性
_MSC_VER: VC 编译器 cl 版本号。VC 2003 编译器版本号 13.10 (_MSC_VER = 1310),VC 2005 编译器版本号 14.00 (_MSC_VER = 1400)。用 cl /? 查看编译器版本号
_MFC_VER: MFC 版本号
_ATL_VER: ATL 版本号
__CLR_VER: CLR 版本号
WINVER: 目标 Windows 版本号
_WIN32_WINNT: 目标 Windows NT 版本号
_WIN32_WINDOWS: 目标 Windows 9x 版本号
_WIN32_IE: 目标 IE 版本号
工程配置管理类^
_DEBUG, NDEBUG: Debug/Release 编译方式
UNICODE, _UNICODE, _MBCS: ANSI/UNICODE/MBCS 字符集支持
_AFXDLL: 动态链接 MFC (DLL)
_ATL_STATIC_REGISTRY, _ATL_DLL: 静态/动态链接 ATL
_DLL: 动态链接 CRT (DLL),对应 /MD、/MDd 编译选项
_MT: CRT 多线程支持,目前 4 种 CRT 链接方式 /MD、/MDd、/MT、/MTd 都支持多线程(VC 2005 已没有单线程版 CRT),加上创建 DLL 模块的 /LD、/LDd,都定义 _MT
_MANAGED: 以 /clr、/clr:pure、/clr:safe 托管方式编译时,定义为 1
__cplusplus_cli: 以 /clr、/clr:pure、/clr:safe 方式编译时定义,VC 2005 中定义为 200406L
上面 1、2、3 类宏通常和条件编译预处理指令 #if/#ifdef/#ifndef 配合使用
辅助类^
__VA_ARGS__: 在函数式宏中,代表变长部分参数 (...),参考 MSDN:
Variadic Macros
__COUNTER__: include 展开编译单元后,编译时第一次遇到 __COUNTER__ 替换为 0,以后在这个编译每遇到一次 __COUNTER__ 自增一。不同的编译单元之间 __COUNTER__ 不互相积累叠加,均从 0 开始计数,但预编译头 .pch 文件会记录 __COUNTER__ 的历史值,则每个编译单元均从历史值 + 1 开始计数。__COUNTER__ 支持宏的嵌套展开
__FUNCTION__, __FUNCDNAME__, __FUNCSIG__: 表示所在函数的函数名的 char 字符串。例如,对于 void test_funcname_macro() 函数原型,它们的值如下:
(1). __FUNCTION__ = test_funcname_macro: 函数的原始名/非修饰名 (undecorated)
(2). __FUNCDNAME__ = ?test_funcname_macro@@YAXXZ: 函数的修饰名 (decorated),可用工具 undname "decorated_name" 得出函数原型和调用规范,即 __FUNCSIG__ 所表示的
(3). __FUNCSIG__ = void __cdecl test_funcname_macro(void): 函数的 signature 名,即调用约定、返回值类型、参数类型
例子:用 __VA_ARGS__ 打印跟踪函数调用^
这个 CALL_TRACE 功能不实用,只为说明 __VA_ARGS__ 用法:
// 针对参数为 void 的函数
// NOTE: 函数 func() 使用 func(__VA_ARGS__) 展开时,会影响前面的变长参数函数 _tprintf(),
// 导致运行时缓冲区访问违例(Debug 方式产生保护中断),所以不能用前两版带 func(__VA_ARGS__) 的 CALL_TRACE
#define CALL_TRACE_VOIDPARM(func, ret) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }
// 针对返回值、参数均为 void 的函数
#define CALL_TRACE_VOID_VOIDPARM(func) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }
// 用户代码
// Unicode 方式编译时,输出 call: CreateFileW,并将返回值传给 hFile
CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
例子:用 __VA_ARGS__ 格式化 std::string^
#define FORMAT_STRING(str, buf, sz, ...) { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_WSTRING(str, buf, sz, ...) { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_TSTRING(str, buf, sz, ...) { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }
// 用户代码
_TCHAR buf[512];
_tstring str;
FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI);
例子:用 __COUNTER__ 计数值定义掩码常量^
这种方法限制很多,并不实用,如 MyMask 之后再定义另一个掩码列举型时,会从 __COUNTER__ 的历史值而非 0 开始:
// 用户代码
enum MyMask
{
MAKE_MASK0(MASK_0), // 2^0: 1
MAKE_MASK(MASK_1), // 2^1: 2 << 0
MAKE_MASK(MASK_2), // 2^2: 2 << 1
MAKE_MASK(MASK_3), // 2^3: 2 << 2
MAKE_MASK(MASK_4) // 2^4: 2 << 3
// 最大 MASK = MASK_31 2^31: 2 << 30
};
例子:用 __FUNCTION__ 打印跟踪函数调用^
// 用户代码
void test_funcname_macro()
{
BEGIN_FUNC
// 函数的功能代码
END_FUNC
}
Windows API 中的注释性宏^
注释性宏,即是否使用它们不影响编译结果,通常定义为空
目的:
(1). 在源代码中起到注解 (annotation) 和标注 (marker) 作用,便于阅读和理解代码功能
(2). 指导 lint 等静态代码检查工具检查代码缺陷
(3). 指导文档自动生成工具扫描源文件,生成类、函数/API 参考文档
如 WinDef.h 中定义的 IN、OUT、OPTIONAL 用来说明函数参数或类型成员的传入、传出、可选性质
sal.h 中有更完整和复杂的注释性宏,SAL (Source code Annotation Language) 参考 sal.h 源文件和 MSDN:
SAL Annotations
Windows API 和 CRT 都用 SAL 注释,几个常用的如下:
__in: 传入参数
__out: 传出参数
__inout: 传入且传出参数
__in_opt, __out_opt, __inout_opt: 可选参数,可以为 NULL
如 CreateFileW() 的声明:
WINBASEAPI
__out
HANDLE
WINAPI
CreateFileW(
__in LPCWSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile
);
Windows API 中的常用宏^
Windows API 包含大量旗标、掩码、状态码、错误码等常量式宏
函数式宏最常用的有:
类型辅助类^
WORD HIWORD(DWORD dwValue)
WORD LOWORD(DWORD dwValue)
WORD MAKEWORD(BYTE bLow, BYTE bHigh)
LONG MAKELONG(WORD wLow, WORD wHigh)
LRESULT MAKELRESULT(WORD wLow, WORD wHigh)
LPARAM MAKELPARAM(WORD wLow, WORD wHigh)
WPARAM MAKEWPARAM(WORD wLow, WORD wHigh)
GDI 类^
LONG DIBINDEX(WORD wColorTableIndex)
COLORREF PALETTEINDEX(WORD wPaletteIndex)
COLORREF PALETTERGB(BYTE bRed, BYTE bGreen, BYTE bBlue)
COLORREF RGB(BYTE byRed, BYTE byGreen, BYTE byBlue)
BYTE GetBValue(DWORD rgb)
BYTE GetGValue(DWORD rgb)
BYTE GetRValue(DWORD rgb)
POINTS MAKEPOINTS(DWORD dwValue)
另外,BITMAP_WIDTHBYTES(bits) 不在 Windows API 中,但比较常用于位图:
错误处理类^
标记没有使用的参数、变量辅助宏^
UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_LOCAL_VARIABLE(V)
让没有使用的参数、变量不产生编译警告,并且关闭 lint 缺陷检查报告
错误码、状态码^
Windows 有三大错误码、状态码空间:
(1). Win32 状态码:GetLastError() 所返回,DWORD 类型,WinError.h 中定义
(2). COM 状态码:COM 函数用,HRESULT 类型,WinError.h 中定义
(3). 内核状态码:内核函数和低级 API 用,NTSTATUS 类型,ntstatus.h 中定义
状态码有关的宏:
HRESULT_FROM_NT(nt_stat): 从 NTSTATUS 变换到 HRESULT
HRESULT_FROM_WIN32(win_err): 从 Win32 状态码变换到 HRESULT
SUCCEEDED(hr): HRESULT 是否表示成功
FAILED(hr): HRESULT 是否表示失败
IS_ERROR(hr): HRESULT 是否表示一个错误
Win32 状态码没有类似 MAKE_HRESULT 的宏,自定义 Win32 状态码时可以用 mc (Message Compiler) 工具处理 .mc 脚本,自动生成含自定义 Win32 状态码的头文件,同时生成用于 FormatMessage() 的状态码文本描述,参考 MSDN:Message Compiler
也可以自定义用于 Win32 状态码的 MAKE_WINERR():
// Win32 状态码的各部分起始位、位掩码和位长度
#define WINERR_SEVERITY_BIT_LOW 30
#define WINERR_SEVERITY_MASK 0xC0000000
#define WINERR_SEVERITY_BIT_LEN 2
#define WINERR_SEVERITY_VALUE(val) (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)
#define WINERR_CUSTOM_DEFINE_BIT_LOW 29
#define WINERR_CUSTOM_DEFINE_MASK 0x20000000
#define WINERR_CUSTOM_DEFINE_BIT_LEN 1
#define WINERR_CUSTOM_DEFINE_FLAG (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)
#define WINERR_FACILITY_BIT_LOW 16
#define WINERR_FACILITY_MASK 0x0FFF0000
#define WINERR_FACILITY_BIT_LEN 12
#define WINERR_FACILITY_VALUE(val) (((val) << WINERR_FACILITY_BIT_LOW) & WINERR_FACILITY_MASK)
#define WINERR_CODE_BIT_LOW 0
#define WINERR_CODE_MASK 0x0000FFFF
#define WINERR_CODE_BIT_LEN 16
#define WINERR_CODE_VALUE(val) (val) & WINERR_CODE_MASK
// Win32 状态码中的严重级别 severity
#define WINERR_SEVERITY_SUCCESS 0
#define WINERR_SEVERITY_INFORM 1
#define WINERR_SEVERITY_WARNING 2
#define WINERR_SEVERITY_ERROR 3
#define WINERR_SEVERITY_NOT_CARE 3
// 自定义 Win32 状态码的宏
#define MAKE_WINERR(sev, fac, code) \
((DWORD)(WINERR_SEVERITY_VALUE(sev) | WINERR_CUSTOM_DEFINE_FLAG | WINERR_FACILITY_VALUE(fac) | WINERR_CODE_VALUE(code)))
调用规范类^
调用规范/约定参考 MSDN:
Calling Conventions
Windows API 使用的调用规范名称宏,在 WinDef.h 中定义:
COM 常用的调用规范辅助宏:
STDAPI: __stdcall,C 链接约定,返回 HRESULT
STDAPI_(type): __stdcall,C 链接约定,返回 type 类型
STDMETHOD(method): __stdcall,返回 HRESULT 的类成员虚函数
STDMETHOD_(type, method): __stdcall,返回 type 类型的类成员虚函数
STDMETHODIMP: __stdcall,返回 HRESULT,对应 STDMETHOD(method) 实现
STDMETHODIMP_(type): __stdcall,返回 type 类型,对应 STDMETHOD_(type, method) 实现
国际化类^
资源类^
网络类^
字符串化操作符 #^
将代码中某个名字转换为字符串字面量,即“加引号”,参考 MSDN:
Stringizing Operator
用 # 操作构造字符串化宏 STRINGIZE^
说明:
(1). # x 产生的是 char 字符串,非 wchar_t 字符串,需配合 _T() 使用
(2). _MACRO() 再次调用 __MACRO() 是一种针对 # 和 ## 操作的常用编写技巧。因为 #、## 操作比较特殊,当它处于宏体中时,不会进行嵌套展开,如 __TSTRINGIZE(NULL) 展开为 "NULL" 而非 "0",要想嵌套展开,再定义一层 _STRINGIZE() 调用 __STRINGIZE() 即可,_TSTRINGIZE(NULL) 展开为 "0"
CRT 中的 STRINGIZE 定义^
CRT 中有类似上面的 STRINGIZE(),以及宽字符化字面量宏 _CRT_WIDE() 的定义:
#ifndef _CRT_WIDE
#define __CRT_WIDE(_String) L ## _String
#define _CRT_WIDE(_String) __CRT_WIDE(_String)
#endif
STRINGIZE 的展开规则^
1. 如果 _STRINGIZE() 的参数是宏,那么宏代表的实际值也将被展开,即嵌套展开
例子:用 STRINGIZE 查看宏的展开结果^
查看某个宏在当前编译配置 (Debug/Release, ANSI/Unicode) 下,实际表示的东西,如某个 _t 系列函数、Windows API 究竟表示哪个函数,可以利用 _STRINGIZE():
// 判断宏的当前值、调用了哪个版本的 _t 系列函数、Windows API
_tprintf(_T("_DEBUG: %s, _UNICODE: %s\n"), _TSTRINGIZE(_DEBUG), _TSTRINGIZE(_UNICODE));
_tprintf(_T("_tprintf: %s\n"), _TSTRINGIZE(_tprintf));
_tprintf(_T("CreateFile: %s\n"), _TSTRINGIZE(CreateFile));
输出结果:
2. 如果 _STRINGIZE() 的参数单纯的变量、函数、类型、const、enum 常量,那么只是将 _STRINGIZE() 括号中的东西加引号而已,如下:
// 枚举常量
enum MUSIC_STATE
{
ST_STOP,
ST_PLAY,
ST_PAUSE,
ST_BUTT
};
// 自定义结构、类
ClassTest obj;
// 函数
void func(int a);
// 下面输出 _TSTRINGIZE() 括号中名字加上引号得到的字符串,而非实际变量值
_tprintf(_T("int: %s, val: %s\n"), _TSTRINGIZE(int), _TSTRINGIZE(val));
_tprintf(_T("MUSIC_STATE: %s, ST_STOP: %s\n"), _TSTRINGIZE(MUSIC_STATE), _TSTRINGIZE(ST_STOP));
_tprintf(_T("ClassTest: %s, obj: %s\n"), _TSTRINGIZE(ClassTest), _TSTRINGIZE(obj));
_tprintf(_T("func: %s\n"), _TSTRINGIZE(func));
输出结果:
拼接操作符 ##^
将代码中两个名字拼接到一起,形成一个名字。## 操作“不加引号”,参考 MSDN:
Token-Pasting Operator
## 与 # 一样对其操作数不进行嵌套展开,所以 __CONCAT(aaa, __CONCAT(bbb, ccc)) 的展开结果是 aaa__CONCAT(bbb, ccc),而 _CONCAT(aaa, _CONCAT(bbb, ccc)) 的展开结果是 aaabbbccc。## 的结果是名字拼接,而不是字符串字面量,即不是 "aaabbbccc"
通常用 ## 操作拼接构造类型、变量、函数的名字
例子:_T() 的定义^
#define _T(x) __T(x)
例子:Windows API 通用句柄类型的定义^
// 因此多数 Windows 句柄是指向桩结构的指针,如 HWND:
// windef.h
DECLARE_HANDLE (HWND);
// HWND 定义展开后是:
struct HWND__
{
int unused;
};
typedef struct HWND__ *HWND;
例子:用 ## 构造函数名^
// 音乐播放状态结构
// 里面有一个用于处理特定状态的回调函数 stat_proc
typedef struct _MusicState
{
MUSIC_STATE stat;
const _TCHAR* stat_name;
int (*stat_proc)(void*);
} MusicState;
// 处理特定音乐播放状态的函数
// 函数名的统一形式 proc_ ## stat,stat 是状态常量的名字
int proc_ST_STOP(void*);
int proc_ST_PLAY(void*);
int proc_ST_PAUSE(void*);
// 初始化音乐播放状态结构
#define INIT_MUSIC_STATE(stat) {stat, _TSTRINGIZE(stat), proc_ ## stat}
MusicState g_MusicState[ST_BUTT] =
{
INIT_MUSIC_STATE(ST_STOP),
INIT_MUSIC_STATE(ST_PLAY),
INIT_MUSIC_STATE(ST_PAUSE)
};
TCHAR 统一字符类型和处理^
_TCHAR、_T()、_t 系列函数等东西叫做 Generic-Text Mapping,即使用宏进行统一字符类型编写,在不同的字符集编码工程配置 ANSI/UNICODE/MBCS 下替换为不同的实际函数或类型,参考 MSDN:Generic-Text Mappings,Using
Generic-Text Mappings,
Using TCHAR.H Data Types with _MBCS
工程的字符集配置的宏定义:
ANSI (SBCS, ASCII): _UNICODE 和 _MBCS 均未定义,使用 char 单字节字符集编码
UNICODE: _UNICODE 定义,使用 wchar_t 宽字符集编码,VC 默认 wchar_t 2 字节
MBCS: _MBCS 定义,使用 char 变长字符集编码,一个字符占一个或多个 char
_TCHAR, _TEXT()/_T(), _t 系列函数^
根据 _UNICODE、_MBCS 的定义,调用 ANSI/UNICODE/MBCS 不同字符集版本的 CRT 函数,或产生字面量,多在 tchar.h 中声明。_t 字符操作函数参考 MSDN:String Manipulation (CRT)
TCHAR, LPTSTR/LPCTSTR, TEXT(), A/W 版本 Windows API^
根据 UNICODE 的定义,调用 ANSI/UNICODE 不同字符集版本的 Windows API,或产生字面量,多在 WinBase.h、Windows.h 中声明
不成文