我应该如何在 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。该函数采用字符数,因此您需要 _countofARRAYSIZE 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 calls FormatMessage 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()?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中正确使用堆栈和堆?

在现代 C++ 应用程序中使用 Win32 代码时,是不是应该使用正确的转换?

如何在 POCO C++ 库中正确使用 OpenSSL

C++,如何从字符串中获取正确的双精度值?

如何在 C++ 中正确使用命名空间?

如何在c中使用qsort比较C++字符串?