¿Cómo obtener el mensaje de error del código de error devuelto por GetLastError ()?

138

Después de una llamada a la API de Windows, ¿cómo puedo obtener el último mensaje de error en forma de texto?

GetLastError() devuelve un valor entero, no un mensaje de texto.

Micha Wiedenmann
fuente
solía haber una búsqueda de error exe en la sección de herramientas en visual studio que lo hace bastante bien cuando solo necesita un mensaje de error para la depuración.
ColdCat
@ColdCat: para la depuración es mucho más fácil agregar un @err,hrreloj y hacer que el depurador convierta automáticamente el último código de error en una representación legible para humanos. El ,hrespecificador de formato funciona para cualquier expresión que se evalúe como un valor integral, por ejemplo, un 5,hrreloj mostrará "ERROR_ACCESS_DENIED: acceso denegado". .
Inspectable el
2
De la GetLastError()documentación: " Para obtener una cadena de error para los códigos de error del sistema, use la FormatMessage()función ". Vea el ejemplo Recuperando el código del último error en MSDN.
Remy Lebeau

Respuestas:

145
//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;
}
Jamin Gray
fuente
2
Creo que realmente necesita pasar (LPSTR)&messageBufferen este caso, ya que de lo contrario no hay forma de que FormatMessageA pueda alterar su valor para apuntar al búfer asignado.
Kylotan
2
Oh, wow, sí, eso es un poco raro. ¿Cómo modificaría el puntero? Pero pasándole la dirección del puntero (puntero a puntero), pero lanzándolo a un puntero normal ... Win32 rareza. Gracias por el aviso, lo arreglé en mi propio código base (y mi respuesta). Captura muy sutil.
Jamin Gray
1
Muchas gracias, su ejemplo es mucho más claro que el de MSDN. Más aún, el de MSDN ni siquiera pudo compilar. Incluye algún strsafe.hencabezado, que no es seguro en absoluto, ya que causa un montón de errores de compilación en winuser.hy winbase.h.
Hi-Angel
2
Algunas ID de error no son compatibles. Por ejemplo, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED causa un nuevo error al llamar a FormatMessage: 0x13D, el sistema no puede encontrar el texto del mensaje para el número de mensaje 0x% 1 en el archivo de mensaje para% 2.
Brent
3
Problemas con esta implementación: 1 GetLastErrorpotencialmente se llama demasiado tarde. 2No hay soporte Unicode. 3Uso de excepciones sin implementar garantías de seguridad excepcionales.
Inspeccionable el
65

Actualizado (11/2017) para tener en cuenta 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);
LeviX
fuente
3
@ Hi-Angel: el ejemplo supone que está compilando con UNICODE definido. 'FormatMessage' es en realidad una macro que se expande a 'FormatMessageA' para las memorias intermedias de caracteres Ansi / MBCS, o 'FormatMessageW' para las memorias intermedias UTF16 / UNICODE, dependiendo de cómo se compila la aplicación. Me tomé la libertad de editar el ejemplo anterior para invocar explícitamente la versión que coincide con el tipo de búfer de salida (wchar_t).
Bukes
2
Agregue FORMAT_MESSAGE_IGNORE_INSERTS: "Si no tiene el control de la cadena de formato, debe pasar FORMAT_MESSAGE_IGNORE_INSERTS para evitar que% 1 cause problemas". blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton
1
Problemas con esta implementación: 1Error al especificar el FORMAT_MESSAGE_IGNORE_INSERTSindicador importante . 2 GetLastErrorpotencialmente llamado demasiado tarde. 3Restricción arbitraria del mensaje a 256 unidades de código.4Sin manejo de errores.
Inspeccionable el
1
sizeof (buf) debe ser ARRAYSIZE (buf) ya que FormatMessage espera el tamaño del búfer en TCHAR, no en bytes.
Kai
1
¿No podemos usar en 256lugar de (sizeof(buf) / sizeof(wchar_t)? ¿Es aceptable y seguro?
Probado en la
14

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 la interfaz considerablemente 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) { ::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.");
    }
}

NOTA: Estas funciones también funcionan para 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:

  • Código de muestra completo, no solo una referencia a la API para llamar.
  • Proporciona implementaciones C y C ++.
  • Funciona para la configuración de proyectos Unicode y MBCS.
  • Toma el código de error como 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 permite que la persona que llama siga el contrato documentado.
  • Implementa la excepción de seguridad 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 FORMAT_MESSAGE_IGNORE_INSERTSbandera. Ver la importancia de la bandera FORMAT_MESSAGE_IGNORE_INSERTS para obtener más información.
  • Manejo adecuado de errores / informe de errores, a diferencia de algunas de las otras respuestas, que silenciosamente ignoran los errores.


Esta respuesta ha sido incorporada de Stack Overflow Documentation. Los siguientes usuarios han contribuido al ejemplo: stackptr , Ajay , Cody Gray ♦ , IInspectable .

Inspectable
fuente
1
En lugar de lanzar std::runtime_error, sugiero lanzar std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")dónde lastErrorestaría el valor de retorno GetLastError()después de la FormatMessage()llamada fallida .
zett42
¿Cuál es la ventaja de usar su versión C FormatMessagedirectamente?
Micha Wiedenmann
@mic: Eso es como preguntar: "¿Cuál es la ventaja de las funciones en C?" Las funciones reducen la complejidad al proporcionar abstracciones. En este caso, la abstracción reduce el número de parámetros de 7 a 3, implementa el contrato documentado de la API al pasar indicadores y parámetros compatibles y codifica la lógica documentada de informe de errores. Proporciona una interfaz concisa, con una semántica fácil de adivinar, ahorrándole la necesidad de invertir 11 minutos para leer la documentación .
Inspeccionable el
Me gusta esta respuesta, está muy claro que se ha prestado especial atención a la corrección del código. Tengo dos preguntas. 1) ¿Por qué lo usa ::HeapFree(::GetProcessHeap(), 0, p)en el eliminador en lugar de ::LocalFree(p)lo que sugiere la documentación? 2) ¿Me doy cuenta de que la documentación dice hacerlo de esta manera, pero no reinterpret_cast<LPTSTR>(&psz)viola la estricta regla de alias?
Teh JoE
@teh: Gracias por los comentarios. Pregunta 1): Honestamente, no sé si esto es seguro. No recuerdo a quién o por qué HeapFreese introdujo la llamada , mientras que esto todavía era un tema en SO Documentation. Tendré que investigar (aunque la documentación parece ser clara, que esto no es seguro). Pregunta 2): Esto es doble. Para una configuración MBCS, LPTSTRes un alias para char*. Ese elenco siempre está a salvo. Para una configuración Unicode, la conversión a wchar_t*es sospechosa en cualquier caso. No sé si esto es seguro en C, pero lo más probable es que no lo sea en C ++. Yo también tendría que investigar esto.
Inspeccionable el
13

En general, debe usar FormatMessagepara convertir un código de error de Win32 a texto.

De la documentación de 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 búfer 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 mensajes basado en un identificador de mensaje y un identificador de idioma. La función copia el texto del mensaje formateado en un búfer de salida, procesando cualquier secuencia de inserción incrustada si se 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
);
Vinay Sajip
fuente
7

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();
        }
    }
}
rboy
fuente
Confirmé que este código funciona. ¿No debería TS aceptar esta respuesta?
swdev
2
Si es necesario para un lanzamiento adicional, hay una manera más simple de hacerlo en C # con Win32Exception
SerG
2
@swdev: ¿Por qué alguien debería aceptar una respuesta en C # a una pregunta etiquetada c o c ++ ? Tal como está, esta respuesta ni siquiera aborda la pregunta que se hace.
Inspeccionable el
1
Bueno, no recuerdo haber votado sobre esta respuesta propuesta. Abordé la falla obvia en la lógica de @ swdev. Pero como no me vas a creer, ahora te lo voy a demostrar: Aquí, haz otro voto negativo. Esa es de mi parte, porque esta respuesta, aunque puede ser útil dada una pregunta diferente, simplemente no es útil dada la pregunta que se hizo.
Inspeccionable el
2
"Supongo que tiene ideas útiles para ofrecer más allá de lo obvio" - De hecho, tengo .
Inspeccionable el
4

Si necesita admitir MBCS y Unicode, la respuesta de Mr.C64 no es suficiente. El búfer debe declararse TCHAR y emitirse 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());
}
victimofleisure
fuente
En caso de que el CStringc'tor arroje una excepción, esta implementación pierde la memoria asignada por la llamada a FormatMessage.
Inspeccionable el
Es cierto, pero he usado este código durante muchos años y nunca ha sido un problema. El único caso en el que es probable que se produzca el CString ctor es la falla en la asignación de memoria, y la mayoría del código MFC, incluidas las cosas proporcionadas por Microsoft, no maneja las condiciones de falta de memoria tan bien como desee. Afortunadamente, la mayoría de las PC ahora tienen tanta memoria que tienes que trabajar bastante duro para usarlo todo. Cualquier uso que genere una instancia CString temporal (incluida la devolución de un CString) corre este riesgo. Es una compensación de riesgo / conveniencia. Además, si sucede en un controlador de mensajes, se detectará la excepción.
victimofleisure
La mayor parte de este comentario está mal, lo siento. "Nunca me pasó" es un punto débil, especialmente cuando sabes cómo puede fallar el código. La cantidad de memoria tampoco tiene impacto en el espacio de direcciones disponible asignado a un proceso. RAM es solo una optimización de rendimiento. Copy-elision evita la asignación de una temporal, cuando el código se escribe para permitir NRVO. El hecho de que las excepciones se encuentren o no en un controlador de mensajes depende del bitness del proceso y la configuración externa. He enviado una respuesta que muestra que la gestión de riesgos no genera inconvenientes.
Inspeccionable el
Además, el riesgo de quedarse sin memoria al construir un temporal es discutible. En ese punto, todos los recursos han sido liberados, y nada malo saldrá de ello.
Inspeccionable el
1
No, lo siento, el código no es útil para ti. Pero TBH es bastante bajo en mi lista de problemas.
victimofleisure
3
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);
}
}
Andriy Kuz
fuente
¿Podría también agregar alguna explicación?
Robert
1
Problemas con esta implementación: 1No hay soporte Unicode. 2Formateo inapropiado del mensaje de error. Si la persona que llama necesita procesar la cadena devuelta, simplemente puede hacerlo. Su implementación deja a la persona que llama sin opción. 3Uso de excepciones pero falta de seguridad de excepción adecuada. En caso de que los std::stringoperadores arrojen excepciones, el búfer asignado por FormatMessagese filtra. 4¿Por qué no simplemente devolver un en std::stringlugar de que la persona que llama pase un objeto por referencia?
Inspeccionable el
1

Desde c ++ 11, puede usar la biblioteca estándar en lugar de 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);
    }
}
Crónica
fuente
Solo hay una pequeña ventana donde las llamadas GetLastErrorproducen un resultado significativo. Siendo esto C ++, la única opción segura aquí es que la persona que llama proporcione el último código de error. Ciertamente no ayuda, que el código presentó llamadas GetLastError dos veces . Además, aunque es conveniente, la falta inherente de C ++ de compatibilidad con caracteres anchos no hace que la error_categoryinterfaz sea universalmente útil. Esto solo se suma al largo historial de oportunidades perdidas de C ++.
Insensible
Buen punto sobre la inútil llamada a GetLastError. Pero no veo diferencia entre llamar GetLastErroraquí o que la persona que llama lo llame. Con respecto a C ++ y wchar: no abandone la esperanza, Microsoft está comenzando a permitir que las aplicaciones sean solo UTF-8 .
Crónica
"No veo ninguna diferencia" - Considere el siguiente sitio llamada: log_error("error", GetLastErrorAsString());. Considere también que log_errorel primer argumento es de tipo std::string. La llamada de conversión (invisible) c'tor acaba de abandonar sus garantías para capturar un valor significativo desde GetLastErrorel punto en el que lo está llamando.
Insensible
¿Por qué crees que el constructor std :: string llama a una función Win32?
Crónica
1
mallocpide HeapAllocel montón de procesos. El montón de procesos es cultivable. Si se necesita para crecer, que en última instancia llamar VirtualAlloc , que no establece último código de error del subproceso de llamada. Ahora, eso es totalmente perder el punto, que es: C ++ es un campo minado. Esta implementación solo se suma a eso, al proporcionar una interfaz con garantías implícitas que simplemente no puede cumplir. Si cree que no hay ningún problema, debería ser fácil para usted probar la corrección de la solución propuesta. Buena suerte.
Insensible
0

Dejaré esto aquí ya que tendré que usarlo más tarde. Es una fuente para una pequeña herramienta compatible binaria que funcionará igualmente 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 */

caso de uso dinámico (se supone que el código de error es válido; de lo contrario, se necesita una comprobación de -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 el ensamblaje gnu como en MinGW32 (de nuevo, se supone que el código de error es válido, de lo contrario se necesita -1 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.

Dmitry
fuente
1
Esto realmente no agrega nada útil. Además de eso, llama a la versión ANSI de FormatMessage, sin razón aparente, y se limita arbitrariamente a 80 caracteres, de nuevo, sin razón alguna. Me temo que esto no es útil.
Inspectable el
Tienes razón, esperaba que nadie se diera cuenta de la falta de la versión Unicode. Veré cómo definir una cadena Unicode en gnu as y modificaré mi solución. Perdón por la deshonestidad.
Dmitry
ok la versión unicode está activada. y no es una razón arbitraria; Todos los mensajes de error tienen menos de 80 caracteres o no vale la pena leerlos, y el código de error es más importante que el mensaje de error. No hay mensajes de error estándar que superen los 80 caracteres, por lo que es una suposición segura, y cuando no lo es, no pierde memoria.
Dmitry
3
"todos los mensajes de error tienen menos de 80 caracteres o no vale la pena leerlos" , eso está mal. El mensaje de error para ERROR_LOCK_VIOLATION(33) es: "El proceso no puede acceder al archivo porque otro proceso ha bloqueado una parte del archivo". Eso es claramente más de 80 caracteres, y vale la pena leerlo, si está tratando de resolver un problema y encontrarlo en un archivo de registro de diagnóstico. Esta respuesta no agrega ningún valor sustancial sobre las respuestas existentes.
Inspectable el
Esto no es menos ingenuo. Simplemente falla con menos frecuencia. Excepto por la implementación del ensamblaje que se basa en el tamaño del búfer (que dice tener espacio para 200 caracteres, cuando solo tiene espacio para 100). Nuevamente, esto no agrega nada sustancial, eso no está en ninguna de las otras respuestas. En particular, es peor que esta respuesta . Vea la lista de viñetas allí y observe qué problemas tiene su implementación propuesta, además de los que acabo de señalar.
Inspeccionable el