如何在C ++中正确使用FormatMessage()?


90

没有

  • 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);
 }

Answers:


134

这是从系统获取错误消息的正确方法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指出,在检索系统消息时切勿使用它们,因为您无法知道系统期望使用哪种插入。

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。


8
当心:此代码使用hResult代替Win32错误代码:这是不同的事情!您可能会收到与实际发生的错误完全不同的错误文本。
Andrei Belogortseff 2014年

1
很好的一点,@ Andrei-实际上,即使错误Win32错误,该例程也只有在系统错误的情况下才会成功-强大的错误处理机制将需要知道错误的来源,检查代码在调用FormatMessage之前,可能要查询其他资源。
Shog9年

1
@AndreiBelogortseff我如何知道在每种情况下使用什么?例如,RegCreateKeyEx返回LONG。它的文档说,我可以FormatMessage用来检索错误,但必须将LONG转换为HRESULT
csl

FormatMessage()采用DWORD @csl,这是一个无符号整数,被认为是有效的错误代码。并非所有返回值(或与此相关的HRESULTS)都将是有效的错误代码;系统假定您已在调用该函数之前对其进行了验证。对RegCreateKeyEx的文档应指定在返回值可以被解释为一个错误......那进行检查第一,然后才是调用的FormatMessage。
Shog9 2015年

1
实际上,MSDN现在提供其版本的相同代码。
ahmd0

14

请记住,您不能执行以下操作:

{
   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
}

7
_com_error两个示例中,对象都是在堆栈上创建的。您正在寻找的术语是临时的。在前面的示例中,对象是一个临时语句,该语句在语句末尾被销毁。
罗伯·肯尼迪

是的,就是那个。但我希望大多数人至少能够从代码中弄清楚这一点。从技术上讲,临时语句不会在语句末尾销毁,而是会在顺序点末尾销毁。(在此示例中这是相同的,因此这只是劈开头发。)
Marius

1
如果您想使其安全(也许不是很有效),可以使用C ++做到这一点:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

试试这个:

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)?
亚伦

1
当然,您可以自己进行这些调整。
oefe

@Atklin:如果要使用参数的hresult,显然不需要第一行(GetLastError())。
David Hanak

4
GetLastError不返回HResult。它返回一个Win32错误代码。最好使用名称PrintLastError,因为它实际上无法处理任何事情。并确保使用FORMAT_MESSAGE_IGNORE_INSERTS。
罗伯·肯尼迪

感谢您的帮助:)-非常感谢
Aaron

5

这是大多数答案的补充,但不要使用LocalFree(errorText)useHeapFree函数:

::HeapFree(::GetProcessHeap(), NULL, errorText);

从MSDN站点

Windows 10
LocalFree不在现代SDK中,因此不能用于释放结果缓冲区。而是使用HeapFree(GetProcessHeap(),locatedMessage)。在这种情况下,这与在内存上调用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标志来整理系统消息中的换行符。

从MSDN站点

FORMAT_MESSAGE_MAX_WIDTH_MASK
该函数将忽略消息定义文本中的常规换行符。该函数将消息定义文本中的硬编码换行符存储到输出缓冲区中。该函数不会产生新的换行符。

更新3
似乎有2种特定的系统错误代码,不能使用推荐的方法返回完整的消息:

为什么FormatMessage只为ERROR_SYSTEM_PROCESS_TERMINATED和ERROR_UNHANDLED_EXCEPTION系统错误创建部分消息?


4

这是处理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), _T("ERROR: %s: %s\n"), msg, err);
    OutputDebugString(buffer);
    LocalFree(err);

}


1
请注意,_sntprintf_s在UNICODE情况下,您没有传递正确的缓冲区大小。该函数的字符数,所以你要_countofARRAYSIZE又名sizeof(buffer) / sizeof(buffer[0])代替sizeof
ThFabba '19

4

从c ++ 11开始,您可以使用标准库代替FormatMessage

#include <system_error>

std::string message = std::system_category().message(hr)

2

如其他答案所指出:

  • FormatMessage接受的DWORD结果不是HRESULT(通常为GetLastError())。
  • LocalFree 需要释放由以下对象分配的内存 FormatMessage

我考虑了以上几点,并为我的回答补充了一些内容:

  • FormatMessagein包装在一个类中,以根据需要分配和释放内存
  • 使用运算符重载(例如,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";

0

下面的代码是代码,与Microsoft的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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.