94

Without:

  • MFC
  • ATL

How can I use FormatMessage() to get the error text for a 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);
 }
Shog9
  • 156,901
  • 35
  • 231
  • 235
Aaron
  • 2,823
  • 9
  • 44
  • 57

8 Answers8

138

Here's the proper way to get an error message back from the system for an HRESULT (named hresult in this case, or you can replace it with 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;
}

The key difference between this and David Hanak's answer is the use of the FORMAT_MESSAGE_IGNORE_INSERTS flag. MSDN is a bit unclear on how insertions should be used, but Raymond Chen notes that you should never use them when retrieving a system message, as you've no way of knowing which insertions the system expects.

FWIW, if you're using Visual C++ you can make your life a bit easier by using the _com_error class:

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();
   
   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Not part of MFC or ATL directly as far as I'm aware.

Euro Micelli
  • 33,285
  • 8
  • 51
  • 70
Shog9
  • 156,901
  • 35
  • 231
  • 235
  • 8
    Beware: this code uses hResult in place of a Win32 error code: those are different things! You may get the text of a completely different error than the one that actually occurred. – Andrei Belogortseff Oct 29 '14 at 23:00
  • 1
    Excellent point, @Andrei - and indeed, even if the error *is* a Win32 error, this routine will only succeed if it's a *system* error - a robust error-handling mechanism would need to be aware of the source of the error, examine the code before calling FormatMessage and perhaps query other sources instead. – Shog9 Oct 30 '14 at 01:14
  • 1
    @AndreiBelogortseff How can I know what to use in each case? For example, `RegCreateKeyEx` returns a `LONG`. Its docs says I can use `FormatMessage` to retrieve the error, but I have to cast the `LONG` into an `HRESULT`. – csl Jun 18 '15 at 06:20
  • FormatMessage() takes a DWORD, @csl, an unsigned integer that is *assumed* to be a valid error code. Not all return values - or HRESULTS for that matter - will be valid error codes; the system assumes you've verified that it is prior to calling the function. The docs for RegCreateKeyEx should specify when the return value can be interpreted as an error... Perform that check *first*, and only then call FormatMessage. – Shog9 Jun 18 '15 at 16:59
  • 1
    Related: When debugging with Visual Studio, you can add the following watch to get the same information: `@err,hr`. Note that this always evaluates the last error code, even when it doesn't contain valid information. You will have to consult the reference documentation to know, when the value is valid. – IInspectable Jul 23 '15 at 15:15
  • Why is the NULL comparison backwards from what it usually is (`var != constant`)? – Yet Another User Oct 30 '16 at 22:35
  • A habit I got into writing software on Windows, back when the compiler wouldn't warn you if you mistyped the operator, @Yet. See: http://stackoverflow.com/questions/370366/why-put-the-constant-before-the-variable-in-a-comparison – Shog9 Nov 04 '16 at 21:31
  • 2
    MSDN actually now provides [their version](https://learn.microsoft.com/en-us/windows/desktop/debug/retrieving-the-last-error-code) of kinda the same code. – ahmd0 Dec 27 '18 at 17:54
14

Keep in mind that you cannot do the following:

{
   LPCTSTR errorText = _com_error(hresult).ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

As the class is created and destroyed on the stack leaving errorText to point to an invalid location. In most cases this location will still contain the error string, but that likelihood falls away fast when writing threaded applications.

So always do it as follows as answered by Shog9 above:

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
  • 3,372
  • 1
  • 30
  • 36
  • 7
    The `_com_error` object is created on the stack in *both* your examples. The term you're looking for is *temporary*. In the former example, the object is a temporary that is destroyed at the end of the statement. – Rob Kennedy Sep 16 '09 at 22:27
  • Yup, meant that. But I'd hope most people would be at least able to figure that out from the code. Technically temporaries are not destroyed at the end of the statement, but at the end of the sequence point. (which is the same thing in this example so this is just splitting hairs.) – Marius Sep 18 '09 at 16:40
  • 1
    If you want to make it _safe_ (maybe not very _efficient_) you can do this in C++: `std::wstring strErrorText = _com_error(hresult).ErrorMessage();` – ahmd0 Dec 27 '18 at 18:22
11

Try this:

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);
}
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
David Hanak
  • 10,754
  • 3
  • 31
  • 39
  • void HandleLastError(hresult)? – Aaron Jan 18 '09 at 17:05
  • 1
    Surely you can make these adaptions yourself. – oefe Jan 18 '09 at 17:12
  • @Atklin: If you want to use hresult from a parameter, you obviously don't need the first line (GetLastError()). – David Hanak Jan 18 '09 at 17:28
  • 5
    GetLastError doesn't return an HResult. It returns a Win32 error code. Might prefer the name PrintLastError since this doesn't actually *handle* anything. And be sure to use FORMAT_MESSAGE_IGNORE_INSERTS. – Rob Kennedy Jan 18 '09 at 17:57
  • OutputDebugString resolves to OutputDebugStringW by default if unicode is on. I'll add an updated version of the function in my answer. – Oleg Zhylin Feb 23 '15 at 19:35
8

Since c++11, you can use the standard library instead of FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Chronial
  • 66,706
  • 14
  • 93
  • 99
  • system_category().message() returns incorrect values compared to FormatMessage. I can't begin to explain how "Input/Output Error" and "Access is denied" are two entirely different things, which is precisely what is returned by system_category().message() for a value of 5 and what FormatMessage returns for a value of 5. How are users to know what they are attempting is impossible for them to do with a value of "Input/Output Error"? "Access is denied" is a FAR more clear indication that they do not have permissions to access the resource they are attempting to acquire. – Jonathan Apr 25 '21 at 14:46
  • The MSVC STL [just calls `FormatMessage` for you](https://github.com/microsoft/STL/blob/62137922ab168f8e23ec1a95c946821e24bde230/stl/src/syserror_import_lib.cpp#L43-L53). They will always return the exact same thing. – Chronial Apr 27 '21 at 07:27
  • perhaps it's that I'm using MSYS? I've tried both, and attempting to access a registry value that I don't have access to as I'm not running the code as Administrator returns 5, and FormatMessage API call returns "Access is denied", but if I feed that into a system_category().message() I get "Input/Output Error" returned. – Jonathan Apr 29 '21 at 16:22
5

This is more an addition to the majority of the answers, but instead of using LocalFree(errorText) use the HeapFree function:

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

From the MSDN site:

Windows 10:
LocalFree is not in the modern SDK, so it cannot be used to free the result buffer. Instead, use HeapFree (GetProcessHeap(), allocatedMessage). In this case, this is the same as calling LocalFree on memory.

Update
I found that LocalFree is in version 10.0.10240.0 of the SDK (line 1108 in WinBase.h). However, the warning still exists in the link above.

#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

Update 2
I would also suggest using the FORMAT_MESSAGE_MAX_WIDTH_MASK flag to tidy up line breaks in system messages.

From the MSDN site:

FORMAT_MESSAGE_MAX_WIDTH_MASK
The function ignores regular line breaks in the message definition text. The function stores hard-coded line breaks in the message definition text into the output buffer. The function generates no new line breaks.

Update 3
There appears to be 2 particular system error codes that do not return the full message using the recommended approach:

Why does FormatMessage only create partial messages for ERROR_SYSTEM_PROCESS_TERMINATED and ERROR_UNHANDLED_EXCEPTION system errors?

Community
  • 1
  • 1
Class Skeleton
  • 2,913
  • 6
  • 31
  • 51
4

Here is a version of David's function that handles Unicode

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);
}
Shane Bishop
  • 3,905
  • 4
  • 17
  • 47
Oleg Zhylin
  • 1,290
  • 12
  • 18
  • 1
    Note that you're not passing the correct buffer size to `_sntprintf_s` in the UNICODE case. The function takes the number of characters, so you want `_countof` or `ARRAYSIZE` aka `sizeof(buffer) / sizeof(buffer[0])` instead of the `sizeof`. – ThFabba Jul 07 '19 at 08:01
2

As pointed out in other answers:

  • FormatMessage takes a DWORD result not a HRESULT (typically GetLastError()).
  • LocalFree is needed to release memory that was allocated by FormatMessage

I took the above points and added a few more for my answer:

  • Wrap the FormatMessage in a class to allocate and release memory as needed
  • Use operator overload (e.g. operator LPTSTR() const { return ...; } so that your class can be used as a string
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;

};

Find a more complete version of the above code here: https://github.com/stephenquan/FormatMessage

With the above class, the usage is simply:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
0

The code below is code is the C++ equivalent I've written out in contrast to Microsoft's ErrorExit() but slightly altered to avoid all macros and use unicode. The idea here is to avoid unnecessary casts and mallocs. I couldn't escape all of the C casts but this is the best I could muster. Pertaining to FormatMessageW(), which requires a pointer to be allocated by the format function and the Error Id from the GetLastError(). The pointer after static_cast can be used like a normal wchar_t pointer.

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