Suppose that you have a Windows system error code, like those returned by GetLastError, and you want to get a descriptive error message associated with it. For example, you may want to show that message to the user via a message box, or write it to some log file, etc. You can invoke the FormatMessage Windows API for that.
FormatMessage is quite versatile, so it’s important to get the various paramaters right.
First, let’s assume that you are working with the native Unicode encoding of Windows APIs, which is UTF-16 (you can always convert to UTF-8 later, for example before writing the error message string to a log file). So, the API to call in this case is FormatMessageW.
As previously stated, FormatMessage is a very versatile API. Here we’ll call it in a specific mode, which is basically requesting the API to allocate a buffer containing the error message, and handing us a pointer to that buffer. It will be our responsibility to release that buffer when it’s no longer needed, invoking the LocalFree API.
Let’s start with the definition of the public interface of a C++ helper function that wraps the FormatMessage invocation details. This function will take as input a system error code, and, on success, will return a std::wstring containing the corresponding descriptive error message. The function prototype looks like this:
std::wstring GetErrorMessage(DWORD errorCode)
Since it would be an error to discard the returned string, if you are using at least C++17, you can mark the function with [[nodiscard]].
[[nodiscard]] std::wstring GetErrorMessage(DWORD errorCode)
Inside the body of the function, we can start declaring a pointer to a wchar_t Unicode UTF-16 null-terminated string, that will store the error message:
wchar_t* pszMessage = nullptr;
This pointer will be placed in the above variable by the FormatMessageW API itself. To request that, we’ll pass a specific flag to FormatMessageW, which is FORMAT_MESSAGE_ALLOCATE_BUFFER.
The call to FormatMessageW looks like this:
DWORD result = ::FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errorCode,
LANG_USER_DEFAULT, // = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
reinterpret_cast<LPWSTR>(&pszMessage), // the message buffer pointer will be written here
0,
nullptr
);
Note that we take the address of the pszMessage local variable (&pszMessage) and pass it to FormatMessageW. The API will allocate the message string and will store the pointer to it in the pszMessage variable. A reinterpret_cast is required since the API parameter is of type LPWSTR (i.e. wchar_t*), but we need another level of indirection here (wchar_t **) for the output pointer parameter.
We use the FORMAT_MESSAGE_FROM_SYSTEM flag to retrieve the message text associated to a Windows system error code (i.e. the errorCode input parameter), like those returned by GetLastError.
The FORMAT_MESSAGE_IGNORE_INSERTS flag is used to let the API know that we want to ignore potential insertion sequences (like %1, %2, …) in the message definition.
The various details of the FormatMessageW API can be found in the official Microsoft documentation.
On error, the API returns zero. So we can add an if statement to process the error case:
if (result == 0)
{
// Error: FormatMessage failed.
// We can throw an exception,
// or return a specific error message...
}
On success, FormatMessageW will store in the pszMessage pointer the address of the error message null-terminated string. At this point, we could simply construct a std::wstring object from it, and return the wstring back to the caller.
However, since the error message string is allocated by FormatMessageW for us, it’s important to free the allocated memory when it’s not needed anymore, to avoid memory leaks. To do so, we must call the LocalFree API, passing the error message string pointer.
In C++, we can safely wrap the LocalFree API call in a simple RAII wrapper, such that the destructor will invoke that function and will automatically free the memory at scope exit.
// Protect the message pointer returned by FormatMessage in safe RAII boundaries.
// LocalFree will be automatically invoked at scope exit.
ScopedLocalPtr messagePtr(pszMessage);
// Return a std::wstring object storing the error message
return pszMessage;
}
Here’s the complete C++ implementation code:
//==========================================================
// C++ Wrapper on the Windows FormatMessage API,
// to get the error message string corresponding
// to a Windows system error code.
//
// by Giovanni Dicanio
//==========================================================
#include <windows.h> // Windows Platform SDK
#include <atlbase.h> // AtlThrowLastWin32
#include <string> // std::wstring
//
// Simple RAII wrapper that automatically invokes LocalFree at scope exit
//
class ScopedLocalPtr
{
public:
// The memory pointed to by the input pointer will be automatically released
// with a call to LocalFree at scope exit
explicit ScopedLocalPtr(void* ptr)
: m_ptr(ptr)
{}
// Automatically invoke LocalFree at scope exit
~ScopedLocalPtr()
{
::LocalFree(reinterpret_cast<HLOCAL>(m_ptr));
}
// Get the wrapped pointer
[[nodiscard]] void* GetPtr() const
{
return m_ptr;
}
//
// Ban copy
//
private:
ScopedLocalPtr(const ScopedLocalPtr&) = delete;
ScopedLocalPtr& operator=(const ScopedLocalPtr&) = delete;
private:
void* m_ptr;
};
//------------------------------------------------------------------------------
// Return an error message corresponding to the input error code.
// The input error code is a system error code like those
// returned by GetLastError.
//------------------------------------------------------------------------------
[[nodiscard]] std::wstring GetErrorMessage(DWORD errorCode)
{
// On successful call to the FormatMessage API,
// this pointer will store the address of the message string corresponding to the errorCode
wchar_t* pszMessage = nullptr;
// Ask FormatMessage to return the error message corresponding to errorCode.
// The error message is stored in a buffer allocated by FormatMessage;
// we are responsible to free it invoking LocalFree.
DWORD result = ::FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errorCode,
LANG_USER_DEFAULT, // = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
reinterpret_cast<LPWSTR>(&pszMessage), // the message buffer pointer will be written here
0,
nullptr
);
if (result == 0)
{
// Error: FormatMessage failed.
// Here I throw an exception. An alternative could be returning a specific error message.
AtlThrowLastWin32();
}
// Protect the message pointer returned by FormatMessage in safe RAII boundaries.
// LocalFree will be automatically invoked at scope exit.
ScopedLocalPtr messagePtr(pszMessage);
// Return a std::wstring object storing the error message
return pszMessage;
}
One thought on “Getting a Descriptive Error Message for a Windows System Error Code”