如何从 GetLastError ()返回的错误代码中获取错误消息?

调用 WindowsAPI 之后,如何获得文本形式的最后一条错误消息?

GetLastError()返回一个整数值,而不是文本消息。

194730 次浏览

FormatMessage 将把 GetLastError的整数返回转换为文本消息。

通常,需要使用 FormatMessage将 Win32错误代码转换为文本。

来自 MSDN 文件:

格式化消息字符串 需要消息定义为 消息定义可以输入 从传递到 它可以来自一条消息 已加载的 模块。或者调用者可以请求 功能,以搜寻系统的 消息表资源 消息定义。该函数查找 消息中的消息定义 基于消息的表资源 标识符和语言标识符。 函数复制格式化的 消息文本到输出缓冲区, 处理任何嵌入式插入 如果需要的话。

格式信息声明:

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

MSDN 有一些示例代码演示如何同时使用 FormatMessage()GetLastError(): 检索最后错误代码

更新(11/2017)以考虑一些意见。

简单的例子:

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

如果使用 c # ,可以使用以下代码:

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

如果您需要支持 MBCS 和 Unicode,C64先生的回答是不够的。缓冲区必须声明为 TCHAR,并强制转换为 LPTSTR。请注意,此代码不处理 Microsoft 附加到错误消息中的恼人的换行符。

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

此外,为了简洁起见,我发现下面的方法很有用:

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

GetLastError 返回一个数值错误代码。要获得描述性错误消息(例如,向用户显示) ,可以调用 格式信息:

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

在 C + + 中,可以通过使用 std: : string 类大大简化接口:

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

注意: 这些函数也适用于 HResult T 值。只需将第一个参数从 DWORDdwErrorCode 更改为 HResult ThResult。代码的其余部分可以保持不变。


与现有答案相比,这些实施工作提供了以下改进:
  • 完整的示例代码,而不仅仅是对要调用的 API 的引用。
  • 同时提供 C 和 C + + 实现。
  • 适用于 Unicode 和 MBCS 项目设置。
  • 将错误代码作为输入参数。这一点很重要,因为线程的最后一个错误代码只有在定义良好的点才有效。输入参数允许调用方遵循文档化的契约。
  • 实现适当的异常安全。与所有其他隐式使用异常的解决方案不同,在构造返回值时引发异常时,此实现不会泄漏内存。
  • 正确使用 FORMAT_MESSAGE_IGNORE_INSERTS标志。有关更多信息,请参见 FORMAT _ MESSAGE _ IGNORE _ INSERTS 标志的重要性
  • 正确的错误处理/错误报告与其他一些答案不同,它们默默地忽略错误。

这个答案是从 Stack Overflow 文档中整合的。下面的用户对示例做出了贡献: Stackptr阿杰Cody Gray我可以检查

我把这个留在这里,因为我以后需要用它。它是一个小型二进制兼容工具的源代码,在汇编、 C 和 C + + 中都能很好地工作。

GetErrorMessageLib.c (编译为 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;
}
}

内联版本(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 */

动态用例(假设错误代码有效,否则需要 -1检查) :

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

常规用例(假设错误代码有效,否则需要 -1返回检查) :

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

例如,与汇编 gnu 一起使用,如 MinGW32中所示(同样,假设错误代码有效,否则需要 -1检查)。

    .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

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

由于 c + + 11,您可以使用标准库而不是 FormatMessage:

#include <system_error>


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

解决方案

下面是使用 std: : string/wstring 的最小 C + + 示例。

  • 可以同时使用 Unicode 和 MBCS
  • 兼容 MSVC 6.0-> VS2022和 GCC/MinGW (兼容-lstdc + +) ,很可能也兼容 Clang。
  • 不需要 C + + 11
  • 适用于 WindowsXP 及更高版本。
#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;
}

但是它没有任何错误检查,如果找不到指定的错误,它只返回一个空字符串。如果愿意,可以实现自己的错误检查。

为什么浪费时间写很多代码,当很少的代码做技巧?


附加信息 (你可以跳过这一步)

我为 dwLanguageId 传递了0,因为这是正确的方法,因为其他的答案没有注意到 MAKELANGID 宏是不推荐的,而 不应使用是不一致的,在某些语言中根本不起作用。

下面是 Windows SDK 10.0.19041.0(2020.05-12)中 winnt.h 的一段摘录,其中说明了这个问题:

//
// ** 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 **
//

看来这些信息还没有传到 MAKELANGID的官方 MSDN 文档中。

即使它工作正常,它也是最糟糕的选项,因为它试图在指定的 LangID 上查找错误字符串,而且只查找一个 ID,如果它不存在,就会失败。而使用0将很可能返回至少 什么的,即使该错误没有本地化为用户的语言。

引自 MSDN 格式信息:

[在] dwLanguageId

请求消息的语言标识符。如果 dwFlags 包含 FORAT _ MESSAGE _ FROM _ STRING,则忽略此参数。

如果在此参数中传递特定的 LANGID,FormatMessage 将仅返回该 LANGID 的消息。如果函数找不到该 LANGID 的消息,则将 Last-ERROR 设置为 ERROR _ RESOURCE _ LANG _ NOT _ FOUND。如果传入0,FormatMessage 将按以下顺序查找 LANGID 的消息:

  1. 语言中立
  2. 线程 LANGID,基于线程的区域设置值
  3. 用户默认 LANGID,基于用户的默认区域设置值
  4. 系统默认 LANGID,基于系统默认区域设置值
  5. 美式英语

如果 FormatMessage 没有找到前面任何 LANGID 的消息,它将返回存在的任何语言消息字符串。如果失败,它将返回 ERROR _ RESOURCE _ LANG _ NOT _ FOUND。