2009-01-18 9 views

Respuesta

123

Aquí es la forma adecuada para obtener un mensaje de error vuelto del sistema para un HRESULT (hresult nombrado en este caso, o puede reemplazarlo con GetLastError()):

LPTSTR errorText = NULL; 

FormatMessage(
    // use system message tables to retrieve error text 
    FORMAT_MESSAGE_FROM_SYSTEM 
    // allocate buffer on local heap for error text 
    |FORMAT_MESSAGE_ALLOCATE_BUFFER 
    // Important! will fail otherwise, since we're not 
    // (and CANNOT) pass insertion parameters 
    |FORMAT_MESSAGE_IGNORE_INSERTS, 
    NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM 
    hresult, 
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
    (LPTSTR)&errorText, // output 
    0, // minimum size for output buffer 
    NULL); // arguments - see note 

if (NULL != errorText) 
{ 
    // ... do something with the string `errorText` - log it, display it to the user, etc. 

    // release memory allocated by FormatMessage() 
    LocalFree(errorText); 
    errorText = NULL; 
} 

La diferencia clave entre esto y la respuesta de David Hanak es el uso de la bandera FORMAT_MESSAGE_IGNORE_INSERTS. MSDN no está nada claro sobre cómo se deben usar las inserciones, pero Raymond Chen notes that you should never use them al recuperar un mensaje del sistema, ya que no tiene forma de saber qué inserciones espera el sistema.

Fwiw, si está utilizando Visual C++ puede hacer su vida un poco más fácil mediante el uso de la clase _com_error:

{ 
    _com_error error(hresult); 
    LPCTSTR errorText = error.ErrorMessage(); 

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 

No forma parte del MFC o ATL directamente Por lo que yo soy consciente.

+4

Cuidado: este código utiliza hResult en lugar de un código de error Win32: ¡esas son cosas diferentes! Puede obtener el texto de un error completamente diferente al que realmente ocurrió. –

+0

Excelente punto, @Andrei - y de hecho, incluso si el error * es * un error de Win32, esta rutina solo tendrá éxito si se trata de un error * system * - un mecanismo robusto de manejo de errores debería conocer la fuente del error, examine el código antes de llamar a FormatMessage y quizás consulte otras fuentes en su lugar. – Shog9

+0

@AndreiBelogortseff ¿Cómo puedo saber qué usar en cada caso? Por ejemplo, 'RegCreateKeyEx' devuelve un' LONG'. Sus documentos dicen que puedo usar 'FormatMessage' para recuperar el error, pero tengo que convertir el' LONG' en 'HRESULT'. – csl

9

Prueba esto:

void PrintLastError (const char *msg /* = "Error occurred" */) { 
     DWORD errCode = GetLastError(); 
     char *err; 
     if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 
          NULL, 
          errCode, 
          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language 
          (LPTSTR) &err, 
          0, 
          NULL)) 
      return; 

     static char buffer[1024]; 
     _snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err); 
     OutputDebugString(buffer); // or otherwise log it 
     LocalFree(err); 
} 
+0

void HandleLastError (hresult)? – Aaron

+0

Hola David, prefiero no usar MFC: "TRACE()" ... – Aaron

+1

Seguramente puedes hacer estas adaptaciones tú mismo. – oefe

13

Tenga en cuenta que no se puede hacer lo siguiente:

{ 
    LPCTSTR errorText = _com_error(hresult).ErrorMessage(); 

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 

A medida que se crea y se destruye en la pila dejando textoError para que apunte a una ubicación no válida la clase. En la mayoría de los casos, esta ubicación aún contendrá la cadena de error, pero esa probabilidad se desvanece rápidamente al escribir aplicaciones con subprocesos.

Así siempre hacerlo de la siguiente manera tan contestada por Shog9 arriba:

{ 
    _com_error error(hresult); 
    LPCTSTR errorText = error.ErrorMessage(); 

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 
+6

El objeto '_com_error' se crea en la pila en * ambos * sus ejemplos . El término que estás buscando es * temporal *. En el ejemplo anterior, el objeto es un temporal que se destruye al final de la declaración. –

+0

Sí, eso significaba. Pero espero que la mayoría de la gente pueda al menos descifrarlo del código. Técnicamente, los temporales no se destruyen al final del enunciado, sino al final del punto de secuencia. (que es lo mismo en este ejemplo, así que esto es solo dividir pelos.) – Marius

+5

btw, _com_error está declarado en 'comdef.h' – Francois

4

Aquí es una versión de la función de David que maneja Unicode

void HandleLastError(const TCHAR *msg /* = "Error occured" */) { 
    DWORD errCode = GetLastError(); 
    TCHAR *err; 
    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 
         NULL, 
         errCode, 
         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language 
         (LPTSTR) &err, 
         0, 
         NULL)) 
     return; 

    //TRACE("ERROR: %s: %s", msg, err); 
    TCHAR buffer[1024]; 
    _sntprintf_s(buffer, sizeof(buffer), _T("ERROR: %s: %s\n"), msg, err); 
    OutputDebugString(buffer); 
    LocalFree(err); 

}

4

Este es más una adición a la mayoría de las respuestas, pero en lugar de usar LocalFree(errorText) utilizar la función HeapFree:

::HeapFree(::GetProcessHeap(), NULL, errorText); 

From the MSDN site:

Windows 10:
LocalFree no está en el SDK moderna, por lo que no se puede utilizar para liberar el búfer resultado. En su lugar, use HeapFree (GetProcessHeap(), assignedMessage). En este caso, esto es lo mismo que llamar a LocalFree en la memoria.

actualización
he encontrado que es LocalFree en la versión 10.0.10240.0 del SDK (línea 1108 en WinBase.h). Sin embargo, la advertencia todavía existe en el enlace de arriba.

#pragma region Desktop Family or OneCore Family 
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) 

WINBASEAPI 
_Success_(return==0) 
_Ret_maybenull_ 
HLOCAL 
WINAPI 
LocalFree(
    _Frees_ptr_opt_ HLOCAL hMem 
    ); 

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */ 
#pragma endregion 

Actualización 2
También me gustaría sugerir el uso de la bandera FORMAT_MESSAGE_MAX_WIDTH_MASK para poner en orden los saltos de línea en los mensajes del sistema.

From the MSDN site:

FORMAT_MESSAGE_MAX_WIDTH_MASK
La función ignora los saltos de línea regular en el texto del mensaje definición. La función almacena saltos de línea codificados en el texto de definición de mensaje en el búfer de salida. La función no genera nuevos saltos de línea.

Actualización 3
Parece que hay 2 códigos de error del sistema particulares que no devuelven el mensaje completo utilizando el método recomendado:

Why does FormatMessage only create partial messages for ERROR_SYSTEM_PROCESS_TERMINATED and ERROR_UNHANDLED_EXCEPTION system errors?

0

El código siguiente es código es el C++ equivalente que he escrito en contraste con Microsoft's ErrorExit() pero ligeramente alterado para evitar todas las macros y usar unicode. La idea aquí es evitar moldes y mallocs innecesarios. No pude escapar de todos los moldes C, pero esto es lo mejor que pude reunir. Perteneciente a FormatMessageW(), que requiere un puntero para ser asignado por la función de formato y el Error Id de GetLastError(). El puntero después de static_cast se puede usar como un puntero wchar_t normal.

#include <string> 
#include <windows.h> 

void __declspec(noreturn) error_exit(const std::wstring FunctionName) 
{ 
    // Retrieve the system error message for the last-error code 
    const DWORD ERROR_ID = GetLastError(); 
    void* MsgBuffer = nullptr; 
    LCID lcid; 
    GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid)); 

    //get error message and attach it to Msgbuffer 
    FormatMessageW(
     FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
     NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL); 
    //concatonate string to DisplayBuffer 
    const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer); 

    // Display the error message and exit the process 
    MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid)); 

    ExitProcess(ERROR_ID); 
} 
Cuestiones relacionadas