How should I use FormatMessage() properly in C++?
Asked Answered
N

8

95

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);
 }
Nenitanenney answered 18/1, 2009 at 16:43 Comment(0)
S
141

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.

Stallard answered 18/1, 2009 at 17:32 Comment(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.Federica
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.Stallard
@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.Obstructionist
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.Stallard
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.Webbing
Why is the NULL comparison backwards from what it usually is (var != constant)?Aec
A habit I got into writing software on Windows, back when the compiler wouldn't warn you if you mistyped the operator, @Yet. See: #370866Stallard
MSDN actually now provides their version of kinda the same code.Afton
P
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
}
Platinumblond answered 16/9, 2009 at 22:15 Comment(3)
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.Straighten
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.)Platinumblond
If you want to make it safe (maybe not very efficient) you can do this in C++: std::wstring strErrorText = _com_error(hresult).ErrorMessage();Afton
G
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);
}
Garica answered 18/1, 2009 at 16:57 Comment(5)
void HandleLastError(hresult)?Nenitanenney
Surely you can make these adaptions yourself.Toname
@Atklin: If you want to use hresult from a parameter, you obviously don't need the first line (GetLastError()).Garica
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.Straighten
OutputDebugString resolves to OutputDebugStringW by default if unicode is on. I'll add an updated version of the function in my answer.Landside
F
8

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

#include <system_error>

std::string message = std::system_category().message(hr)
Fiddlefaddle answered 4/6, 2019 at 22:59 Comment(3)
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.Corneous
The MSVC STL just calls FormatMessage for you. They will always return the exact same thing.Fiddlefaddle
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.Corneous
N
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?

Nationality answered 21/7, 2015 at 13:57 Comment(0)
L
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);
}
Landside answered 23/2, 2015 at 19:36 Comment(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.Those
C
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";
Corny answered 15/4, 2019 at 1:34 Comment(0)
A
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);
}
Antionetteantioxidant answered 15/11, 2017 at 4:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.