我应该如何在 C++ 中正确使用 FormatMessage()?
Posted
技术标签:
【中文标题】我应该如何在 C++ 中正确使用 FormatMessage()?【英文标题】:How should I use FormatMessage() properly in C++? 【发布时间】:2010-10-02 02:38:41 【问题描述】:没有:
MFC ATL如何使用FormatMessage()
获取HRESULT
的错误文本?
HRESULT hresult = application.CreateInstance("Excel.Application");
if (FAILED(hresult))
// what should i put here to obtain a human-readable
// description of the error?
exit (hresult);
【问题讨论】:
【参考方案1】:这是处理 Unicode 的 David 函数的一个版本
void HandleLastError(const TCHAR *msg /* = "Error occured" */)
DWORD errCode = GetLastError();
TCHAR *err;
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPTSTR) &err,
0,
NULL))
return;
//TRACE("ERROR: %s: %s", msg, err);
TCHAR buffer[1024];
_sntprintf_s(buffer, sizeof(buffer) / sizeof(buffer[0]), _T("ERROR: %s: %s\n"), msg, err);
OutputDebugString(buffer);
LocalFree(err);
【讨论】:
请注意,在 UNICODE 情况下,您没有将正确的缓冲区大小传递给_sntprintf_s
。该函数采用字符数,因此您需要 _countof
或 ARRAYSIZE
aka sizeof(buffer) / sizeof(buffer[0])
而不是 sizeof
。【参考方案2】:
以下是从系统返回错误消息以获取 HRESULT
(在本例中命名为 hresult,或者您可以将其替换为 GetLastError()
)的正确方法:
LPTSTR errorText = NULL;
FormatMessage(
// use system message tables to retrieve error text
FORMAT_MESSAGE_FROM_SYSTEM
// allocate buffer on local heap for error text
|FORMAT_MESSAGE_ALLOCATE_BUFFER
// Important! will fail otherwise, since we're not
// (and CANNOT) pass insertion parameters
|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
hresult,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorText, // output
0, // minimum size for output buffer
NULL); // arguments - see note
if ( NULL != errorText )
// ... do something with the string `errorText` - log it, display it to the user, etc.
// release memory allocated by FormatMessage()
LocalFree(errorText);
errorText = NULL;
这与 David Hanak 的答案之间的主要区别在于使用了 FORMAT_MESSAGE_IGNORE_INSERTS
标志。 MSDN 对如何使用插入有点不清楚,但Raymond Chen notes that you should never use them 在检索系统消息时,因为您无法知道系统期望哪些插入。
FWIW,如果您使用 Visual C++,您可以使用 _com_error
类让您的生活更轻松:
_com_error error(hresult);
LPCTSTR errorText = error.ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
据我所知,它不是 MFC 或 ATL 的一部分。
【讨论】:
当心:此代码使用 hResult 代替 Win32 错误代码:它们是不同的东西!您可能会收到与实际发生的错误完全不同的错误文本。 好点,@Andrei - 事实上,即使错误 is 是 Win32 错误,该例程也只有在 system 错误时才会成功- 一个强大的错误处理机制需要知道错误的来源,在调用 FormatMessage 之前检查代码,并可能查询其他来源。 @AndreiBelogorseff 我怎么知道在每种情况下使用什么?例如,RegCreateKeyEx
返回一个LONG
。它的文档说我可以使用FormatMessage
来检索错误,但我必须将LONG
转换为HRESULT
。
FormatMessage() 采用 DWORD、@csl、一个假定为有效错误代码的无符号整数。并非所有返回值(或 HRESULTS)都是有效的错误代码;系统假定您在调用函数之前已经验证过它。 RegCreateKeyEx 的文档应指定何时可以将返回值解释为错误...执行该检查首先,然后才调用 FormatMessage。
MSDN 现在实际上提供了 their version 的代码。【参考方案3】:
正如其他答案中指出的那样:
FormatMessage
采用 DWORD
结果而不是 HRESULT
(通常为 GetLastError()
)。
需要LocalFree
来释放由FormatMessage
分配的内存
我采纳了以上几点,并为我的答案添加了一些:
将FormatMessage
包装在一个类中以根据需要分配和释放内存
使用运算符重载(例如operator LPTSTR() const return ...;
,以便您的类可以用作字符串
class CFormatMessage
public:
CFormatMessage(DWORD dwMessageId,
DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) :
m_text(NULL)
Assign(dwMessageId, dwLanguageId);
~CFormatMessage()
Clear();
void Clear()
if (m_text)
LocalFree(m_text);
m_text = NULL;
void Assign(DWORD dwMessageId,
DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))
Clear();
DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS,
FormatMessage(
dwFlags,
NULL,
dwMessageId,
dwLanguageId,
(LPTSTR) &m_text,
0,
NULL);
LPTSTR text() const return m_text;
operator LPTSTR() const return text();
protected:
LPTSTR m_text;
;
在此处找到上述代码的更完整版本:https://github.com/stephenquan/FormatMessage
有了上面的类,用法很简单:
std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
【讨论】:
【参考方案4】:从c++11开始,可以使用标准库代替FormatMessage
:
#include <system_error>
std::string message = std::system_category().message(hr)
【讨论】:
system_category().message() 与 FormatMessage 相比返回的值不正确。我无法开始解释“输入/输出错误”和“访问被拒绝”如何是两个完全不同的东西,这正是 system_category().message() 返回的值为 5 和 FormatMessage 返回的内容值为 5。如果值为“输入/输出错误”,用户如何知道他们正在尝试的操作是不可能的? “访问被拒绝”更清楚地表明他们无权访问他们试图获取的资源。 MSVC STL just callsFormatMessage
for you。他们总是会返回完全相同的东西。
可能是我用的是MSYS?我已经尝试了这两种方法,并尝试访问我无权访问的注册表值,因为我没有运行代码,因为管理员返回 5,并且 FormatMessage API 调用返回“访问被拒绝”,但是如果我提供了进入 system_category().message() 我得到“输入/输出错误”返回。【参考方案5】:
下面的代码是我写出的与Microsoft's ErrorExit() 对比的C++ 等效代码,但稍作改动以避免使用所有宏并使用unicode。这里的想法是避免不必要的强制转换和 malloc。我无法逃脱所有的 C 演员阵容,但这是我能召集的最好的。与 FormatMessageW() 相关,它需要由格式函数分配的指针和来自 GetLastError() 的错误 Id。 static_cast 之后的指针可以像普通的 wchar_t 指针一样使用。
#include <string>
#include <windows.h>
void __declspec(noreturn) error_exit(const std::wstring FunctionName)
// Retrieve the system error message for the last-error code
const DWORD ERROR_ID = GetLastError();
void* MsgBuffer = nullptr;
LCID lcid;
GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid));
//get error message and attach it to Msgbuffer
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL);
//concatonate string to DisplayBuffer
const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer);
// Display the error message and exit the process
MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid));
ExitProcess(ERROR_ID);
【讨论】:
【参考方案6】:这更多是对大多数答案的补充,但不要使用LocalFree(errorText)
,而是使用HeapFree
函数:
::HeapFree(::GetProcessHeap(), NULL, errorText);
From the MSDN site:
Windows 10: LocalFree 不在现代 SDK 中,因此不能用于释放结果缓冲区。相反,使用 HeapFree (GetProcessHeap(),allocateMessage)。在这种情况下,这与在内存上调用 LocalFree 相同。
更新
我发现LocalFree
在 SDK 的 10.0.10240.0 版本中(WinBase.h 中的第 1108 行)。但是,警告仍然存在于上面的链接中。
#pragma region Desktop Family or OneCore Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
WINBASEAPI
_Success_(return==0)
_Ret_maybenull_
HLOCAL
WINAPI
LocalFree(
_Frees_ptr_opt_ HLOCAL hMem
);
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */
#pragma endregion
更新 2
我还建议使用FORMAT_MESSAGE_MAX_WIDTH_MASK
标志来整理系统消息中的换行符。
From the MSDN site:
FORMAT_MESSAGE_MAX_WIDTH_MASK 该函数忽略消息定义文本中的常规换行符。该函数将消息定义文本中的硬编码换行符存储到输出缓冲区中。该函数不生成新的换行符。
更新 3 似乎有 2 个特定的系统错误代码未使用推荐的方法返回完整消息:
Why does FormatMessage only create partial messages for ERROR_SYSTEM_PROCESS_TERMINATED and ERROR_UNHANDLED_EXCEPTION system errors?
【讨论】:
【参考方案7】:试试这个:
void PrintLastError (const char *msg /* = "Error occurred" */)
DWORD errCode = GetLastError();
char *err;
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPTSTR) &err,
0,
NULL))
return;
static char buffer[1024];
_snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err);
OutputDebugString(buffer); // or otherwise log it
LocalFree(err);
【讨论】:
void HandleLastError(hresult)? 您当然可以自己进行这些改编。 @Atklin:如果你想从一个参数中使用 hresult,你显然不需要第一行(GetLastError())。 GetLastError 不返回 HResult。它返回一个 Win32 错误代码。可能更喜欢 PrintLastError 这个名字,因为它实际上并没有 handle 任何东西。并确保使用 FORMAT_MESSAGE_IGNORE_INSERTS。 感谢你们的帮助 :) - 非常感谢【参考方案8】:请记住,您不能执行以下操作:
LPCTSTR errorText = _com_error(hresult).ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
当类在堆栈上创建和销毁时,errorText 指向一个无效的位置。在大多数情况下,此位置仍会包含错误字符串,但在编写线程应用程序时,这种可能性会很快消失。
所以总是按照上面 Shog9 的回答如下:
_com_error error(hresult);
LPCTSTR errorText = error.ErrorMessage();
// do something with the error...
//automatic cleanup when error goes out of scope
【讨论】:
_com_error
对象是在 both 示例中的堆栈上创建的。您要查找的术语是temporary。在前一个例子中,对象是一个临时对象,在语句结束时被销毁。
是的,就是这个意思。但我希望大多数人至少能够从代码中弄清楚这一点。从技术上讲,临时对象不会在语句结束时被销毁,而是在序列点结束时被销毁。 (在这个例子中是一样的,所以这只是分裂头发。)
如果你想让它安全(可能不是很高效)你可以在C++中做到这一点:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
以上是关于我应该如何在 C++ 中正确使用 FormatMessage()?的主要内容,如果未能解决你的问题,请参考以下文章