2010-12-17 82 views
5

我有一个Visual Studio 2008 C++项目,该项目在出现异常错误时使用Win32Exception类。该Win32Exception类看起来是这样的:将GetLastError()转换为异常

/// 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 

类效果很好它已经被使用,我想知道的是,如果有任何明显的情况下,这将失败,我应该知道什么情况。 。欢迎任何其他陷阱,注意事项或有关改进的一般建议。

请注意,boost代码库不适用于此代码。

+0

如果你想知道,这个类也用于没有'FormatMessageA'的WindowsMo​​bile。这就是为什么它从UNICODE转换为ASCII。 – PaulH 2010-12-17 22:12:20

回答

3

注意,如果back_inserter原因std::bad_alloc被抛出,内存内FormatMessage分配被泄露。

+1

+1的内存泄漏我已经错过了!但是,您发布链接的代码不是线程安全的,它使用静态变量来存储what()的结果。 – ybungalobill 2010-12-18 16:22:53

+0

@ybungalobill:是的,它不是线程安全的 - 我只在单线程应用程序中使用它。 (并且线程安全性的缺乏在它旁边的注释中清楚地标出:P)如果您需要线程安全性,则可以在异常对象本身内存储一个'std :: string',我选择不要为这个特定库,以便在引发异常时不必为std :: string结构付费。如果您需要线程安全性,只需调用'GetCharMessage()',而不是返回一个'std :: string'。 – 2010-12-18 16:34:10

+0

对不起,我在阅读代码时没有阅读评论:P讨厌的噪音。做到这一点的正确方法是有一个'mutable'缓存,并在第一次调用what()时填充它。 – ybungalobill 2010-12-18 16:39:23

3
  • 真是巧合!我在我的所有项目中都使用了类似的代码!这实际上是一个好主意。

  • 这段代码是有问题的:

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

    它只是trancates WCHARs到字符。你可以明确地使用FormatMessageA在当前代码页中获取消息(好吧,你不能像你说的那样),或者制定所有你的stings都是UTF-8编码的约定。我选择了以后,请看this为什么。

  • 错误消息本身可能没有用。捕获堆栈跟踪可能是一个好主意。

+0

从std :: runtime_error派生出来的'std :: exception'定义'what()'函数是'const wchar_t * what()'。这就是我将字符串转换为ASCII的原因。还是你说我应该使用除'std :: copy()'以外的字符串转换?我想可以切换到':: wcstombs()'。这在当时似乎更容易。 – PaulH 2010-12-17 22:27:49

+0

是的,使用wcstombs,WideCharToMultiByte或者像现在这样拷贝并且确认文本实际上是ascii。虽然FormatMessage可能会在任何理智的系统上返回ASCII,但这并不重要。 – ybungalobill 2010-12-17 22:39:41

+0

@PaulH:这是'为const char *''从what',不'wchar_t'(我希望它的工作那样)困难'wcstombs'是,你不知道它什么期望源编码,而你没有知道它转换为什么编码。考虑到你正在使用Win32特定的场景,你不妨使用FormatMessageA。 – 2010-12-18 00:31:59

1
  • FormatMessage本身可能会失败。对于这种情况,可能会出现一些中性的“代码为%d的未知错误”。
  • 某些错误代码并非真正的错误(ERROR_ALREADY_EXISTS),具体取决于用户的期望。
  • 某些系统函数返回它们自己的错误代码(值得注意的例子是SHFileOperation),您必须单独处理它们。如果你想让他们得到处理,那就是。
  • 考虑在异常内部有附加信息:哪里是从(源文件和行)抛出的异常,什么系统函数导致异常,函数的参数是什么(至少是标识的,如文件名,句柄值,或一些这样的)。堆栈跟踪也很好。
+1

<角落都舍不得亏>“用代码%d未知错误”其实我觉得应该是%U,或为0x%8X是真的很酷。 2010-12-18 00:22:24

1

我想知道的是,如果有 任何明显的情况下,这将 失败,我应该知道的。任何 其他陷阱,警告或一般 改进建议是 欢迎。

我遇到过的这个邮件检索的主要问题是ERROR_SUCCESS。当某些操作失败时,这会非常复杂,并附带错误消息“操作成功”。人们不会认为这会发生,但确实如此。

我猜这是Dialecticus指出的一个特殊情况,即“某些错误代码并非真正的错误”,但对于大多数代码来说,至少该消息通常是可接受的。

第二个问题是大多数Windows系统错误消息的末尾都有一个回车符+换行符。将消息插入到其他文本中存在问题,并且违反了C++异常消息的约定。所以,删除这些字符是个好主意。

现在,不是重复其他人已经注意到的所有内容,而是重复一些关于设计的内容。

如果将ErrorMessage函数公开或从类中移出,并按值取出错误代码,而不是采用指针参数,则该函数将更加有用。这是分开承担责任的原则。促进重用。

如果您使用析构函数来释放内存,那么ErrorMessage中的代码将更清晰,更安全,更高效。然后,您也可以直接在return声明中构造字符串,而不是使用带后置插入器的复制循环。

干杯&心连心,

0

我最近正在研究一个非常类似的类,并在阅读此线程后试图使复制部分异常安全。我引入了一个小辅助类,它除了保留指向由::FormatMessage返回的字符串的指针外,还将其与::LocalFree在析构函数中释放。复制,分配和移动是不允许的,所以人们不会陷入困境。

这里是我想出了总数:

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)"); 
    } 
} 

也许这将是有用的一些你。

0

意识到这是旧的,但至少有VC++ 2015年,你可以抛出一个system_error,会做这一切与system_category()功能:

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

这将打印:“无法写入文件:访问是否认“

相关问题