如何在不使用unchecked cast的情况下调用Windows FormatMessage API?
虽然FormatMessage的函数签名看起来接受普通指针(比如wchar_t*),但当指定FORMAT_MESSAGE_ALLOCATE_BUFFER标志时,它实际需要的是指针的地址(即wchar_t**)。要避免C风格的不安全强制转换,你可以使用C++的reinterpret_cast来明确转换类型,这比C风格转换更具可读性和类型安全性。
以下是针对宽字符(UTF-16)版本的类型安全实现:
void ErrorExit() { DWORD dw = GetLastError(); LPWSTR lpMsgBuf = nullptr; // 明确使用宽字符指针类型 if (FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&lpMsgBuf), // 使用reinterpret_cast明确转换 0, NULL) == 0) { MessageBoxW(NULL, L"FormatMessage执行失败", L"错误", MB_OK); ExitProcess(dw); } MessageBoxW(NULL, lpMsgBuf, L"错误", MB_OK); LocalFree(lpMsgBuf); ExitProcess(dw); }
如果你使用多字节字符集(不推荐,现代Windows更推荐UTF-16或UTF-8),也可以用FormatMessageA做类似处理:
void ErrorExit() { DWORD dw = GetLastError(); LPSTR lpMsgBuf = nullptr; if (FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPSTR>(&lpMsgBuf), 0, NULL) == 0) { MessageBoxA(NULL, "FormatMessage执行失败", "错误", MB_OK); ExitProcess(dw); } MessageBoxA(NULL, lpMsgBuf, "错误", MB_OK); LocalFree(lpMsgBuf); ExitProcess(dw); }
reinterpret_cast是C中专门用于这种底层类型转换的操作符,它明确告知编译器你清楚正在进行的类型转换,相比C风格的(LPTSTR)转换,更符合现代C的类型安全规范。
在Windows 10 1903(版本18362)及以上,FormatMessageA支持直接生成UTF-8编码的错误消息,无需额外转换。你只需要确保程序的代码页设置为UTF-8(通过SetConsoleOutputCP(CP_UTF8)或在项目属性中设置“UTF-8 无签名”编码),并调用FormatMessageA即可:
#include <string> #include <windows.h> std::string GetLastErrorUtf8() { DWORD errorId = GetLastError(); if (errorId == 0) { return ""; } LPSTR msgBuffer = nullptr; size_t msgSize = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPSTR>(&msgBuffer), 0, NULL); std::string errorMsg(msgBuffer, msgSize); LocalFree(msgBuffer); return errorMsg; }
对于Windows 10 1903之前的系统,FormatMessageA默认使用系统的ANSI代码页,无法直接生成UTF-8。此时你需要先调用FormatMessageW获取UTF-16字符串,再通过WideCharToMultiByte转换为UTF-8:
#include <string> #include <windows.h> std::string GetLastErrorUtf8() { DWORD errorId = GetLastError(); if (errorId == 0) { return ""; } LPWSTR wideMsgBuffer = nullptr; size_t wideMsgSize = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&wideMsgBuffer), 0, NULL); if (wideMsgSize == 0) { LocalFree(wideMsgBuffer); return "FormatMessage执行失败"; } // 计算UTF-8所需的缓冲区大小 int utf8Size = WideCharToMultiByte(CP_UTF8, 0, wideMsgBuffer, static_cast<int>(wideMsgSize), NULL, 0, NULL, NULL); std::string utf8Msg(utf8Size, '\0'); WideCharToMultiByte(CP_UTF8, 0, wideMsgBuffer, static_cast<int>(wideMsgSize), utf8Msg.data(), utf8Size, NULL, NULL); LocalFree(wideMsgBuffer); return utf8Msg; }
目前Win32生态中没有完全替代FormatMessage的“现代”API,对于Win32函数的错误处理,GetLastError+FormatMessage仍是标准方案。但你可以将其封装成现代C++风格的函数(比如返回std::string/std::wstring),避免直接重复编写底层转换代码。
内容的提问来源于stack exchange,提问作者Zsar

