How to get the error message from the error code returned by GetLastError()?

C++CWinapi

C++ Problem Overview


After a Windows API call, how can I get the last error message in a textual form?

GetLastError() returns an integer value, not a text message.

C++ Solutions


Solution 1 - C++

//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString()
{
	//Get the error message ID, if any.
	DWORD errorMessageID = ::GetLastError();
	if(errorMessageID == 0) {
		return std::string(); //No error message has been recorded
	}
    
	LPSTR messageBuffer = nullptr;

    //Ask Win32 to give us the string version of that message ID.
    //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
	size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
								 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
	
    //Copy the error message into a std::string.
	std::string message(messageBuffer, size);
	
	//Free the Win32's string's buffer.
	LocalFree(messageBuffer);
			
	return message;
}

Solution 2 - C++

Updated (11/2017) to take into consideration some comments.

Easy example:

wchar_t buf[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
               NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
               buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

Solution 3 - C++

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

#include <system_error>

if (!SomeWin32Function()){
   DWORD error = ::GetLastError();
   std::string message = std::system_category().message(error);
   ...
}

Solution 4 - C++

MSDN has some sample code that demonstrates how to use FormatMessage() and GetLastError() together: Retrieving the Last-Error Code

Solution 5 - C++

GetLastError returns a numerical error code. To obtain a descriptive error message (e.g., to display to a user), you can call FormatMessage:

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

In C++, you can simplify the interface considerably by using the std::string class:

#include <Windows.h>
#include <system_error>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz{ nullptr };
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::LocalFree(p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        auto error_code{ ::GetLastError() };
        throw std::system_error( error_code, std::system_category(),
                                 "Failed to retrieve error message string.");
    }
}

NOTE: These functions also work for HRESULT values. Just change the first parameter from DWORD dwErrorCode to HRESULT hResult. The rest of the code can remain unchanged.


These implementations provide the following improvements over the existing answers:

  • Complete sample code, not just a reference to the API to call.
  • Provides both C and C++ implementations.
  • Works for both Unicode and MBCS project settings.
  • Takes the error code as an input parameter. This is important, as a thread's last error code is only valid at well defined points. An input parameter allows the caller to follow the documented contract.
  • Implements proper exception safety. Unlike all of the other solutions that implicitly use exceptions, this implementation will not leak memory in case an exception is thrown while constructing the return value.
  • Proper use of the FORMAT_MESSAGE_IGNORE_INSERTS flag. See The importance of the FORMAT_MESSAGE_IGNORE_INSERTS flag for more information.
  • Proper error handling/error reporting, unlike some of the other answers, that silently ignore errors.

This answer has been incorporated from Stack Overflow Documentation. The following users have contributed to the example: stackptr, Ajay, Cody Gray♦, IInspectable.

Solution 6 - C++

FormatMessage will turn GetLastError's integer return into a text message.

Solution 7 - C++

In general, you need to use FormatMessage to convert from a Win32 error code to text.

From the MSDN documentation:

> Formats a message string. The function > requires a message definition as > input. The message definition can come > from a buffer passed into the > function. It can come from a message > table resource in an already-loaded > module. Or the caller can ask the > function to search the system's > message table resource(s) for the > message definition. The function finds > the message definition in a message > table resource based on a message > identifier and a language identifier. > The function copies the formatted > message text to an output buffer, > processing any embedded insert > sequences if requested.

The declaration of FormatMessage:

DWORD WINAPI FormatMessage(
  __in      DWORD dwFlags,
  __in_opt  LPCVOID lpSource,
  __in      DWORD dwMessageId, // your error code
  __in      DWORD dwLanguageId,
  __out     LPTSTR lpBuffer,
  __in      DWORD nSize,
  __in_opt  va_list *Arguments
);

Solution 8 - C++

If you're using c# you can use this code:

using System.Runtime.InteropServices;

public static class WinErrors
{
    #region definitions
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr LocalFree(IntPtr hMem);
    
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int FormatMessage(FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, ref IntPtr lpBuffer, uint nSize, IntPtr Arguments);

    [Flags]
    private enum FormatMessageFlags : uint
    {
        FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100,
        FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200,
        FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000,
        FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000,
        FORMAT_MESSAGE_FROM_HMODULE = 0x00000800,
        FORMAT_MESSAGE_FROM_STRING = 0x00000400,
    }
    #endregion

    /// <summary>
    /// Gets a user friendly string message for a system error code
    /// </summary>
    /// <param name="errorCode">System error code</param>
    /// <returns>Error string</returns>
    public static string GetSystemMessage(int errorCode)
    {
        try
        {
            IntPtr lpMsgBuf = IntPtr.Zero;

            int dwChars = FormatMessage(
                FormatMessageFlags.FORMAT_MESSAGE_ALLOCATE_BUFFER | FormatMessageFlags.FORMAT_MESSAGE_FROM_SYSTEM | FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS,
                IntPtr.Zero,
                (uint) errorCode,
                0, // Default language
                ref lpMsgBuf,
                0,
                IntPtr.Zero);
            if (dwChars == 0)
            {
                // Handle the error.
                int le = Marshal.GetLastWin32Error();
                return "Unable to get error code string from System - Error " + le.ToString();
            }

            string sRet = Marshal.PtrToStringAnsi(lpMsgBuf);

            // Free the buffer.
            lpMsgBuf = LocalFree(lpMsgBuf);
            return sRet;
        }
        catch (Exception e)
        {
            return "Unable to get error code string from System -> " + e.ToString();
        }
    }
}

Solution 9 - C++

If you need to support MBCS as well as Unicode, Mr.C64's answer is not quite enough. The buffer must be declared TCHAR, and cast to LPTSTR. Note that this code doesn't deal with the annoying newline that Microsoft appends to the error message.

CString FormatErrorMessage(DWORD ErrorCode)
{
    TCHAR	*pMsgBuf = NULL;
    DWORD	nMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		reinterpret_cast<LPTSTR>(&pMsgBuf), 0, NULL);
    if (!nMsgLen)
        return _T("FormatMessage fail");
    CString sMsg(pMsgBuf, nMsgLen);
    LocalFree(pMsgBuf);
    return sMsg;
}

Also, for brevity I find the following method useful:

CString GetLastErrorString()
{
	return FormatErrorMessage(GetLastError());
}

Solution 10 - C++

void WinErrorCodeToString(DWORD ErrorCode, string& Message)
{
char* locbuffer = NULL;
DWORD count = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, ErrorCode,
	0, (LPSTR)&locbuffer, 0, nullptr);
if (locbuffer)
{
	if (count)
	{
		int c;
		int back = 0;
		//
		// strip any trailing "\r\n"s and replace by a single "\n"
		//
		while (((c = *CharPrevA(locbuffer, locbuffer + count)) == '\r') ||
			(c == '\n')) {
			count--;
			back++;
		}

		if (back) {
			locbuffer[count++] = '\n';
			locbuffer[count] = '\0';
		}

		Message = "Error: ";
		Message += locbuffer;
	}
	LocalFree(locbuffer);
}
else
{
	Message = "Unknown error code: " + to_string(ErrorCode);
}
}

Solution 11 - C++

Solution

Here is my minimal C++ example using std::string/wstring.

  • Works with both Unicode and MBCS
  • Compatible from MSVC 6.0 -> VS2022 and GCC/MinGW (with -lstdc++). Most likely with Clang too.
  • Doesn't require C++11
  • Works in Windows XP and later.
#include <windows.h>
#include <string>

typedef std::basic_string<TCHAR> String;

String errorMessage(DWORD dwError)
{
    LPTSTR lpBuffer = NULL;
    String ret = TEXT("");
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwError, 0, (LPTSTR)&lpBuffer, 0, NULL))
        ret = String(lpBuffer);
    LocalFree(lpBuffer);
    return ret;
}

It doesn't have any error checking though and just returns an empty string if it can't find the specified error. You can implement your own error checking if you like.

Why waste time write lot code, when few code do trick?


Additional information (you can skip this)

I pass 0 for dwLanguageId as it's the right way to do it, as other answers failed to notice that MAKELANGID macro is deprecated and should not be used as it is inconsistent and doesn't work at all for some languages.

Here is an excerpt from winnt.h in Windows SDK 10.0.19041.0 (2020-05-12) stating the issue:

//
// ** DEPRECATED ** DEPRECATED ** DEPRECATED ** DEPRECATED ** DEPRECATED **
//
//  DEPRECATED: The LCID/LANGID/SORTID concept is deprecated, please use
//  Locale Names instead, eg: "en-US" instead of an LCID like 0x0409.
//  See the documentation for GetLocaleInfoEx.
//
//  A language ID is a 16 bit value which is the combination of a
//  primary language ID and a secondary language ID.  The bits are
//  allocated as follows:
//
//       +-----------------------+-------------------------+
//       |     Sublanguage ID    |   Primary Language ID   |
//       +-----------------------+-------------------------+
//        15                   10 9                       0   bit
//
//  WARNING:  This pattern is broken and not followed for all languages.
//            Serbian, Bosnian & Croatian are a few examples.
//
//  WARNING:  There are > 6000 human languages.  The PRIMARYLANGID construct
//            cannot support all languages your application may encounter.
//            Please use Language Names, such as "en".
//
//  WARNING:  There are > 200 country-regions.  The SUBLANGID construct cannot
//            represent all valid dialects of languages such as English.
//            Please use Locale Names, such as "en-US".
//
//  WARNING:  Some languages may have more than one PRIMARYLANGID.  Please
//            use Locale Names, such as "en-FJ".
//
//  WARNING:  Some languages do not have assigned LANGIDs.  Please use
//            Locale Names, such as "tlh-Piqd".
//
//  It is recommended that applications test for locale names rather than
//  attempting to construct/deconstruct LANGID/PRIMARYLANGID/SUBLANGID
//
//  Language ID creation/extraction macros:
//
//    MAKELANGID    - construct language id from a primary language id and
//                    a sublanguage id.
//    PRIMARYLANGID - extract primary language id from a language id.
//    SUBLANGID     - extract sublanguage id from a language id.
//
//  Note that the LANG, SUBLANG construction is not always consistent.
//  The named locale APIs (eg GetLocaleInfoEx) are recommended.
//
//  DEPRECATED: Language IDs do not exist for all locales
//
// ** DEPRECATED ** DEPRECATED ** DEPRECATED ** DEPRECATED ** DEPRECATED **
//

Seems just that the information hasn't made its' way to the official MSDN doc of MAKELANGID yet.

Even if it did work correctly, it's the worse option since it tries to find the error string on that specified LangID and only that one ID, failing if it doesn't exist. Using 0 instead will very likely return at least something, even if that error isn't localized to the user's language.

Quote from MSDN FormatMessageW: > [in] dwLanguageId > > The language identifier for the requested message. This parameter is ignored if dwFlags includes FORMAT_MESSAGE_FROM_STRING. > > If you pass a specific LANGID in this parameter, FormatMessage will return a message for that LANGID only. If the function cannot find a message for that LANGID, it sets Last-Error to ERROR_RESOURCE_LANG_NOT_FOUND. If you pass in zero, FormatMessage looks for a message for LANGIDs in the following order: > > 1. Language neutral > 2. Thread LANGID, based on the thread's locale value > 3. User default LANGID, based on the user's default locale value > 4. System default LANGID, based on the system default locale value > 5. US English > > If FormatMessage does not locate a message for any of the preceding LANGIDs, it returns any language message string that is present. If that fails, it returns ERROR_RESOURCE_LANG_NOT_FOUND.

Solution 12 - C++

i'll leave this here since i will need to use it later. It's a source for a small binary compatible tool that will work equally well in assembly, C and C++.

GetErrorMessageLib.c (compiled to GetErrorMessageLib.dll)

#include <Windows.h>

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;
    
    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }
    
    // FormatMessage's return is 1 character too short.
    ++result_len;
    
    strncpy(lpResult, tmp, dwBytes);
    
    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);
    
    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;
    
    nchars = dwBytes >> 1;
    
    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    
    
    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;
    
    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);
    
    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

inline version(GetErrorMessage.h):

#ifndef GetErrorMessage_H 
#define GetErrorMessage_H 
#include <Windows.h>    

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;
    
    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }
    
    // FormatMessage's return is 1 character too short.
    ++result_len;
    
    strncpy(lpResult, tmp, dwBytes);
    
    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);
    
    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;
    
    nchars = dwBytes >> 1;
    
    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    
    
    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;
    
    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);
    
    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

#endif /* GetErrorMessage_H */

dynamic usecase(assumed that error code is valid, otherwise a -1 check is needed):

#include <Windows.h>
#include <Winbase.h>
#include <assert.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    int (*GetErrorMessageA)(DWORD, LPSTR, DWORD);
    int (*GetErrorMessageW)(DWORD, LPWSTR, DWORD);
    char result1[260];
    wchar_t result2[260];

    assert(LoadLibraryA("GetErrorMessageLib.dll"));

    GetErrorMessageA = (int (*)(DWORD, LPSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageA"
    );        
    GetErrorMessageW = (int (*)(DWORD, LPWSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageW"
    );        

    GetErrorMessageA(33, result1, sizeof(result1));
    GetErrorMessageW(33, result2, sizeof(result2));

    puts(result1);
    _putws(result2);

    return 0;
}

regular use case(assumes error code is valid, otherwise -1 return check is needed):

#include <stdio.h>
#include "GetErrorMessage.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    char result1[260];
    wchar_t result2[260];

    GetErrorMessageA(33, result1, sizeof(result1));
    puts(result1);
    
    GetErrorMessageW(33, result2, sizeof(result2));
    _putws(result2);

    return 0;
}

example using with assembly gnu as in MinGW32(again, assumed that error code is valid, otherwise -1 check is needed).

    .global _WinMain@16

    .section .text
_WinMain@16:
    // eax = LoadLibraryA("GetErrorMessageLib.dll")
    push $sz0
    call _LoadLibraryA@4 // stdcall, no cleanup needed

    // eax = GetProcAddress(eax, "GetErrorMessageW")
    push $sz1
    push %eax
    call _GetProcAddress@8 // stdcall, no cleanup needed

    // (*eax)(errorCode, szErrorMessage)
    push $200
    push $szErrorMessage
    push errorCode       
    call *%eax // cdecl, cleanup needed
    add $12, %esp

    push $szErrorMessage
    call __putws // cdecl, cleanup needed
    add $4, %esp

    ret $16

    .section .rodata
sz0: .asciz "GetErrorMessageLib.dll"    
sz1: .asciz "GetErrorMessageW"
errorCode: .long 33

    .section .data
szErrorMessage: .space 200

result: The process cannot access the file because another process has locked a portion of the file.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJenaView Question on Stackoverflow
Solution 1 - C++Jamin GreyView Answer on Stackoverflow
Solution 2 - C++LeviXView Answer on Stackoverflow
Solution 3 - C++ChronialView Answer on Stackoverflow
Solution 4 - C++bk1eView Answer on Stackoverflow
Solution 5 - C++IInspectableView Answer on Stackoverflow
Solution 6 - C++Jonathan GraehlView Answer on Stackoverflow
Solution 7 - C++Vinay SajipView Answer on Stackoverflow
Solution 8 - C++rboyView Answer on Stackoverflow
Solution 9 - C++victimofleisureView Answer on Stackoverflow
Solution 10 - C++Andriy KuzView Answer on Stackoverflow
Solution 11 - C++anzz1View Answer on Stackoverflow
Solution 12 - C++DmitryView Answer on Stackoverflow