Windows API调用后,如何获得文本形式的最后一条错误消息?
GetLastError()
返回整数值,而不是文本消息。
@err,hr
监视程序,并使调试器将最后的错误代码自动转换为人类可读的表示,就会容易得多。该,hr
格式说明适用于任何表达式,其值的积分值,如5,hr
表会显示“ERROR_ACCESS_DENIED:访问被拒绝。” 。
Windows API调用后,如何获得文本形式的最后一条错误消息?
GetLastError()
返回整数值,而不是文本消息。
@err,hr
监视程序,并使调试器将最后的错误代码自动转换为人类可读的表示,就会容易得多。该,hr
格式说明适用于任何表达式,其值的积分值,如5,hr
表会显示“ERROR_ACCESS_DENIED:访问被拒绝。” 。
Answers:
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString()
{
//Get the error message, if any.
DWORD errorMessageID = ::GetLastError();
if(errorMessageID == 0)
return std::string(); //No error message has been recorded
LPSTR messageBuffer = nullptr;
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);
std::string message(messageBuffer, size);
//Free the buffer.
LocalFree(messageBuffer);
return message;
}
(LPSTR)&messageBuffer
在这种情况下通过,否则,FormatMessageA不可能更改其值以指向分配的缓冲区。
strsafe.h
根本不安全的标头,它在winuser.h
和中引起了许多编译器错误winbase.h
。
1
GetLastError
可能被称为为时已晚。2
不支持Unicode。3
在没有实施例外安全保证的情况下使用例外。
已更新(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);
1
无法指定重要FORMAT_MESSAGE_IGNORE_INSERTS
标志。2
GetLastError
可能为时已晚。3
消息的任意限制为256个代码单元。4
没有错误处理。
256
代替代替(sizeof(buf) / sizeof(wchar_t)
吗?它可以接受并且安全吗?
FormatMessage会将GetLastError的整数返回值转换为文本消息。
GetLastError返回数字错误代码。要获取描述性错误消息(例如,显示给用户),可以调用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);
}
在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值。只需将第一个参数从DWORD dwErrorCode更改为HRESULT hResult。其余代码可以保持不变。
FORMAT_MESSAGE_IGNORE_INSERTS
标志。有关更多信息,请参见FORMAT_MESSAGE_IGNORE_INSERTS标志的重要性。std::runtime_error
我建议不要抛出,而是将失败调用后的返回值丢到std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")
哪里。lastError
GetLastError()
FormatMessage()
FormatMessage
直接使用C版本的优势是什么?
::HeapFree(::GetProcessHeap(), 0, p)
在删除器中使用而不是::LocalFree(p)
文档中所建议的?2)我意识到文档说可以这样做,但是没有reinterpret_cast<LPTSTR>(&psz)
违反严格的别名规则吗?
HeapFree
引入了谁或为什么调用此方法,尽管这仍然是SO Documentation中的主题。我将不得不进行调查(尽管文档似乎很清楚,但这并不安全)。问题2):这有两个方面。对于MBCS配置,LPTSTR
是的别名char*
。那演员总是安全的。对于Unicode配置,wchar_t*
无论如何,投射到都是腥的。我不知道这在C语言中是否安全,但很可能在C ++中不是。我也必须对此进行调查。
通常,您需要使用FormatMessage
来将Win32错误代码转换为文本。
从MSDN 文档中:
格式化消息字符串。该功能需要消息定义作为输入。消息定义可以来自传递给函数的缓冲区。它可以来自已加载模块中的消息表资源。或者,调用者可以要求函数在系统的消息表资源中搜索消息定义。该函数根据消息标识符和语言标识符在消息表资源中找到消息定义。该函数将格式化的消息文本复制到输出缓冲区,如果需要,则处理所有嵌入的插入序列。
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
);
如果您使用的是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();
}
}
}
如果您需要支持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());
}
CString
c'tor抛出异常,此实现会泄漏对的调用分配的内存FormatMessage
。
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);
}
}
1
不支持Unicode。2
错误消息的格式不正确。如果调用方需要处理返回的字符串,则可以执行此操作。您的实现使调用者无法选择。3
使用例外,但缺乏适当的例外安全性。万一std::string
操作员抛出异常,则由分配的缓冲区将FormatMessage
泄漏。4
为什么不简单地返回a std::string
而不是让调用者通过引用传递对象呢?
从c ++ 11开始,您可以使用标准库代替FormatMessage
:
#include <system_error>
std::string GetLastErrorAsString(){
DWORD errorMessageID = ::GetLastError();
if (errorMessageID == 0) {
return std::string(); //No error message has been recorded
} else {
return std::system_category().message(errorMessageID);
}
}
GetLastError
产生有意义的结果。使用C ++,这里唯一安全的选择是让调用者提供最新的错误代码。给出的代码调用GetLastError
两次肯定没有帮助。同样,尽管方便,但C ++固有的缺乏宽字符支持的功能未能使该error_category
接口具有通用性。这只会增加C ++错失良机的悠久历史。
GetLastError
。但是我看不到在GetLastError
这里打电话还是让呼叫者打电话。关于C ++和wchar:不要放弃希望,Microsoft开始允许应用程序仅使用UTF-8。
log_error("error", GetLastErrorAsString());
。还要考虑log_error
第一个参数是类型std::string
。(不可见的)转换调用只是放弃了您的保证,即从GetLastError
您调用它的那一刻开始捕获有意义的值。
malloc
调用HeapAlloc
进程堆。进程堆是可增长的。如果需要增长,它将最终调用VirtualAlloc,它会设置调用线程的最后一个错误代码。现在,这完全没有意义了:C ++是一个雷区。通过为接口提供隐含的保证,该实现只是增加了它根本无法辜负的义务。如果您认为没有问题,那么应该容易证明拟议解决方案的正确性。祝好运。
我将其留在这里,因为以后需要使用它。它是一个小型二进制兼容工具的源代码,该工具在汇编语言,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;
}
与MinGW32中的程序集gnu一起使用的示例(同样,假定错误代码有效,否则需要-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.
FormatMessage
无缘无故地调用的ANSI版本,并且再无故地将其自身限制为80个字符。恐怕这没有帮助。
ERROR_LOCK_VIOLATION
(33)的错误消息是:“该进程无法访问文件,因为另一个进程已锁定了文件的一部分。” 如果您尝试解决问题并在诊断日志文件中找到该字符,则它们显然都超过80个字符,并且非常值得阅读。与现有答案相比,该答案没有任何实质性价值。