c++ - tutorial - winapi que es
¿Cómo obtener el mensaje de error del código de error devuelto por GetLastError()? (10)
Después de una llamada a la API de Windows, ¿cómo puedo obtener el último mensaje de error en una forma textual?
GetLastError()
devuelve un valor entero, no un mensaje de texto.
Gracias.
Dejaré esto aquí porque tendré que usarlo más tarde. Es una fuente para una pequeña herramienta compatible binaria que funcionará igual de bien en ensamblaje, C y C ++.
GetErrorMessageLib.c (compilado a 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;
}
}
versión en línea (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 */
uso dinámico (se supone que el código de error es válido; de lo contrario, se necesita una verificación -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;
}
caso de uso regular (se supone que el código de error es válido; de lo contrario, se necesita -1 verificación de retorno):
#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;
}
ejemplo que se usa con asamblea gnu como en MinGW32 (de nuevo, se supone que el código de error es válido, de lo contrario -1 se necesita verificación).
.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
resultado: The process cannot access the file because another process has locked a portion of the file.
En general, debe usar FormatMessage
para convertir de un código de error Win32 a texto.
De la documentation MSDN:
Formatea una cadena de mensaje. La función requiere una definición de mensaje como entrada. La definición del mensaje puede provenir de un buffer pasado a la función. Puede provenir de un recurso de tabla de mensajes en un módulo ya cargado. O la persona que llama puede pedirle a la función que busque los recursos de la tabla de mensajes del sistema para la definición del mensaje. La función encuentra la definición del mensaje en un recurso de tabla de mensaje basado en un identificador de mensaje y un identificador de idioma. La función copia el texto del mensaje formateado a un búfer de salida, procesando cualquier secuencia insertada insertada si así lo solicita.
La declaración de 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
);
MSDN tiene un código de muestra que muestra cómo usar FormatMessage()
y GetLastError()
juntos: Recuperando el código de último error
Si necesita admitir MBCS y Unicode, la respuesta del Sr.C64 no es suficiente. El buffer debe declararse TCHAR y enviarse a LPTSTR. Tenga en cuenta que este código no trata con la molesta nueva línea que Microsoft agrega al mensaje de error.
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;
}
Además, por brevedad encuentro útil el siguiente método:
CString GetLastErrorString()
{
return FormatErrorMessage(GetLastError());
}
Si usa c #, puede usar este código:
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();
}
}
}
FormatMessage convertirá el retorno entero GetLastError en un mensaje de texto.
GetLastError() devuelve un código de error numérico. Para obtener un mensaje de error descriptivo (por ejemplo, para mostrar a un usuario), puede llamar a 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);
}
En C ++, puede simplificar considerablemente la interfaz utilizando la clase 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) { ::HeapFree(::GetProcessHeap(), 0, 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.");
}
}
NOTA: Estas funciones también funcionan para los valores HRESULT. Simplemente cambie el primer parámetro de DWORD dwErrorCode a HRESULT hResult. El resto del código puede permanecer sin cambios.
Estas implementaciones proporcionan las siguientes mejoras sobre las respuestas existentes:- Complete el código de muestra, no solo una referencia a la API para llamar.
- Proporciona implementaciones C y C ++.
- Funciona para las configuraciones de proyecto de Unicode y MBCS.
- Toma el código de error como un parámetro de entrada. Esto es importante, ya que el último código de error de un hilo solo es válido en puntos bien definidos. Un parámetro de entrada le permite a la persona que llama seguir el contrato documentado.
- Implementa la seguridad de excepción adecuada. A diferencia de todas las otras soluciones que implícitamente usan excepciones, esta implementación no perderá memoria en caso de que se genere una excepción al construir el valor de retorno.
- Uso adecuado de la bandera
FORMAT_MESSAGE_IGNORE_INSERTS
. Consulte blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353 para obtener más información. - Correcto manejo de errores / informes de errores, a diferencia de algunas de las otras respuestas, que silenciosamente ignoran los errores.
Actualizado (11/2017) para tomar en consideración algunos comentarios.
Ejemplo fácil:
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, 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;
}
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);
}
}