2010-12-17 10 views
5

Tengo un proyecto de Visual Studio 2008 C++ que utiliza una clase Win32Exception en los casos en que hay un error excepcional. La clase Win32Exception se ve así:Convertir GetLastError() en una excepción

/// defines an exception based on Win32 error codes. The what() function will 
/// return a formatted string returned from FormatMessage() 
class Win32Exception : public std::runtime_error 
{ 
public: 
    Win32Exception() : std::runtime_error(ErrorMessage(&error_code_)) 
    { 
    }; 

    virtual ~Win32Exception() { }; 

    /// return the actual error code 
    DWORD ErrorCode() const throw() { return error_code_; }; 

private: 

    static std::string ErrorMessage(DWORD* error_code) 
    { 
     *error_code = ::GetLastError(); 

     std::string error_messageA; 
     wchar_t* error_messageW = NULL; 
     DWORD len = ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | 
             FORMAT_MESSAGE_ALLOCATE_BUFFER | 
             FORMAT_MESSAGE_IGNORE_INSERTS, 
             NULL, 
             *error_code, 
             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
             reinterpret_cast<LPWSTR>(&error_messageW), 
             0, 
             NULL); 
     if(NULL != error_messageW) 
     { 
      // this may generate a C4244 warning. It is safe to ignore. 
      std::copy(error_messageW, 
         error_messageW + len, 
         std::back_inserter(error_messageA)); 
      ::LocalFree(error_messageW); 
     } 
     return error_messageA; 
    }; 

    /// error code returned by GetLastError() 
    DWORD error_code_; 

}; // class Win32Exception 

la clase funciona bien en las situaciones que se ha utilizado en Lo que me gustaría saber es si hay casos obvios en los que se producirá un error que debería tener en cuenta. . Cualquier otro inconveniente, advertencia o sugerencias generales sobre mejoras son bienvenidas.

Tenga en cuenta que la biblioteca de impulso no es una opción para este código.

+0

En caso de que se lo pregunte, esta clase también se usa en WindowsMobile que no tiene' FormatMessageA'. Es por eso que se convierte de UNICODE a ASCII. – PaulH

Respuesta

3

Tenga en cuenta que si las causas back_inserterstd::bad_alloc a ser lanzados, la memoria asignada dentro FormatMessage se filtró.

+1

+1 por la pérdida de memoria que me he perdido. Sin embargo, el código al que ha publicado un enlace no es seguro para subprocesos, usa una variable estática para almacenar el resultado de qué(). – ybungalobill

+0

@ybungalobill: Sí, no es seguro para subprocesos; lo estoy usando solo en una aplicación con subprocesos. (Y la falta de seguridad del hilo está claramente marcada en el comentario al lado: P) Si necesita seguridad de hilo, puede almacenar un 'std :: string' dentro del objeto de excepción, que elegí no hacer para este particular biblioteca para que no tenga que pagar construcciones 'std :: string' cuando se lanza una excepción. Si necesita seguridad de subprocesos, simplemente llame a 'GetCharMessage()' en su lugar, que devuelve un 'std :: string' en su lugar. –

+0

Lo siento, no leo los comentarios cuando leo el código: P odio el ruido. La forma correcta de hacerlo es tener un caché "mutable" y llenarlo cuando se llame a 'what()' por primera vez. – ybungalobill

3
  • ¡Qué casualidad! ¡Utilizo un código similar en todos mis proyectos! En realidad es una buena idea.

  • Este código es problemático:

    // this may generate a C4244 warning. It is safe to ignore. 
        std::copy(error_messageW, 
           error_messageW + len, 
           std::back_inserter(error_messageA)); 
    

    Sólo trancates wchars de caracteres. Tu puedes usar FormatMessageA explícitamente para recibir un mensaje en la página de códigos actual (vale, no puedes hacerlo como dijiste), o hacer una convención de que todas tus picaduras están codificadas en UTF-8. Elegí el más tarde, vea this por qué.

  • El mensaje de error en sí mismo puede no ser útil. Capturar el trazado de la pila puede ser una buena idea.

+0

La 'std :: exception' de la que se deriva' std :: runtime_error' define la función 'what()' como 'const wchar_t * what()'. Es por eso que convertí la cadena a ASCII. ¿O está diciendo que debería usar algo que no sea 'std :: copy()' para la conversión de cadenas? Podría cambiar a ':: wcstombs()', supongo. Esto simplemente parecía más fácil en ese momento. – PaulH

+0

Sí, use wcstombs, WideCharToMultiByte o copie como lo hace ahora y verifique que el texto sea realmente ascii. Sin embargo, no es crítico ya que FormatMessage probablemente devuelve ASCII en cualquier sistema sano. – ybungalobill

+0

@PaulH: Es 'const char *' de 'what', no' wchar_t' (ojalá funcionara de esa manera) La dificultad con 'wcstombs' es que no sabes qué fuente de codificación espera, y no lo haces saber a qué codificación se convierte. Teniendo en cuenta que está trabajando con un escenario específico de Win32 de todos modos, también podría usar FormatMessageA. –

1
  • FormatMessage vez puede fallar. Algún neutro "Error desconocido con el código% d" podría estar en orden para tal caso.
  • Algunos códigos de error no son realmente errores (ERROR_ALREADY_EXISTS), dependiendo de lo que el usuario está esperando.
  • Algunas funciones del sistema devuelven sus propios códigos de error (ejemplo notable es SHFileOperation) que debe manejar por separado. Si quieres que sean manejados, eso es.
  • Considere tener información adicional dentro de la excepción: ¿dónde se produce la excepción desde (archivo fuente y línea), qué función del sistema causó excepción, cuáles fueron los parámetros de la función (al menos los identificadores, como nombre de archivo, valor de manejo, o algo así). Stack trace también es bueno.
+1

"Error desconocido con el código% d" en realidad, creo que debería ser% u, o 0x% 8x para ser realmente genial.

1

Lo que me gustaría saber es si hay alguna son casos obvios en que ello pueda fallan que debería tener en cuenta. Cualquier otras advertencias, advertencias o sugerencias generales sobre sobre mejoras son bienvenida.

El principal problema que he tenido con tal recuperación de mensajes ha sido ERROR_SUCCESS. Es bastante desconcertante cuando algunas operaciones fallan, acompañado por el mensaje de error "La operación fue exitosa". Uno no pensaría que eso podría suceder, pero lo hace.

Supongo que este es un caso especial de lo que Dialecticus observó, que "Algunos códigos de error no son realmente errores", pero para la mayoría de esos códigos, al menos, el mensaje es generalmente aceptable.

El segundo problema es que la mayoría del mensaje de error del sistema de Windows tiene un retorno de carro + salto de línea al final.Es problemático para la inserción de mensajes en otro texto, y rompe la convención para los mensajes de excepción de C++. Entonces, buena idea para eliminar esos caracteres.

Ahora, en lugar de repetir todo lo que otros ya han notado, unas palabras sobre el diseño.

La función ErrorMessage sería mucho más útil si se hiciera pública o fuera de la clase, y tomara el código de error por valor, en lugar de tomar el argumento del puntero. Este es el principio de mantener separadas las responsabilidades por separado. Promueve la reutilización.

El código en ErrorMessage sería más claro y seguro y eficiente si utilizó un destructor para desasignar la memoria. Entonces también podría simplemente construir la cadena directamente en la instrucción return en lugar de usar un bucle de copia con el insertador posterior.

Saludos & HTH.,

0

Hace poco estuve trabajando en una clase muy similar y después de leer este hilo intenté hacer que la parte copiadora fuera excepcionalmente segura. Introduje una pequeña clase de ayuda que no hace más que mantener el puntero a la cadena devuelta por ::FormatMessage y liberarla con ::LocalFree en su destructor. No se permite copiar, asignar y mover, por lo que uno no puede meterse en problemas.

Esto es lo que me ocurrió en total:

class windows_error { 
public: 
    windows_error(wchar_t const* what); 

    // Getter functions 
    unsigned long errorCode() const { return _code; } 
    wchar_t const* description() const { return _what; } 
    std::wstring errorMessage() const { return _sys_err_msg; } 
private: 
    unsigned long _code; 
    wchar_t const* _what; 
    std::wstring _sys_err_msg; 
}; 

// This class outsources the problem of managing the string which 
// was allocated with ::LocalAlloc by the ::FormatMessage function. 
// This is necessary to make the constructor of windows_error exception-safe. 
class LocalAllocHelper { 
public: 
    LocalAllocHelper(wchar_t* string) : _string(string) { } 
    ~LocalAllocHelper() { 
     ::LocalFree(_string); 
    } 

    LocalAllocHelper(LocalAllocHelper const& other) = delete; 
    LocalAllocHelper(LocalAllocHelper && other) = delete; 
    LocalAllocHelper& operator=(LocalAllocHelper const& other) = delete; 
    LocalAllocHelper& operator=(LocalAllocHelper && other) = delete; 

private: 
    wchar_t* _string; 
}; 

windows_error::windows_error(wchar_t const* what) 
    : _code(::GetLastError()), 
     _what(what) { 
    // Create a temporary pointer to a wide string for the error message 
    LPWSTR _temp_msg = 0; 
    // Retrieve error message from error code and save the length 
    // of the buffer which is being returned. This is needed to 
    // implement the copy and assignment constructor. 
    DWORD _buffer_size = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
              FORMAT_MESSAGE_FROM_SYSTEM | 
              FORMAT_MESSAGE_IGNORE_INSERTS, 
              NULL, _code, 0, _temp_msg, 0, NULL); 

    if(_buffer_size) { 
     // When calling _sys_err_msg.resize an exception could be thrown therefore 
     // the _temp_msg needs to be a managed resource. 
     LocalAllocHelper helper(_temp_msg); 
     _sys_err_msg.resize(_buffer_size + 1); 
     std::copy(_temp_msg, _temp_msg + _buffer_size, _sys_err_msg.begin()); 
    } 
    else { 
     _sys_err_msg = std::wstring(L"Unknown error. (FormatMessage failed)"); 
    } 
} 

Tal vez esto será útil para algunos de ustedes.

0

darse cuenta de esto es viejo, pero al menos con VC++ 2015 se puede tirar un system_error que va a hacer todo esto con la función system_category():

try 
{ 
    throw system_error(E_ACCESSDENIED, system_category(), "Failed to write file"); 
} 
catch (exception& ex) 
{ 
    cout << ex.what(); 
} 

Esto imprimiría: "Error al escribir el archivo: Acceso denegado "

Cuestiones relacionadas