【问题标题】:How should I use FormatMessage() properly in C++?我应该如何在 C++ 中正确使用 FormatMessage()?
【发布时间】: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);
 }

【问题讨论】:

    标签: c++ windows error-handling formatmessage


    【解决方案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();
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-14
          • 2021-12-03
          • 2013-07-10
          • 2012-07-22
          • 1970-01-01
          相关资源
          最近更新 更多