将GetLastError()转换为异常

时间:2010-12-17 22:05:56

标签: c++ exception error-handling

我有一个Visual Studio 2008 C ++项目,在出现例外错误的情况下使用Win32Exception类。 Win32Exception类看起来像这样:

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

该课程在其使用的情况下运作良好。我想知道的是,如果有任何明显的情况会导致失败,我应该知道。欢迎任何其他陷阱,警告或改进的一般建议。

请注意,升级库不是此代码的选项。

6 个答案:

答案 0 :(得分:5)

请注意,如果back_inserter导致std::bad_alloc被抛出,则FormatMessage内分配的内存将被泄露。

答案 1 :(得分:3)

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

  • 此代码存在问题:

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

    它只是将WCHAR转换为字符。你可以显式地使用FormatMessageA来获取当前代码页中的消息(好吧,你不能像你说的那样),或者使你的所有行为都是UTF-8编码的约定。我之后选择了this为什么。

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

  

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

我在使用此类邮件检索时遇到的主要问题是ERROR_SUCCESS。当某些操作失败时,它会相当令人困惑,并伴有错误消息“操作成功”。人们不会认为这可能会发生,但确实如此。

我想这是Dialecticus注意到的一个特例,“有些错误代码并非真正的错误”,但对于大多数这些代码,至少该消息通常是可以接受的。

第二个问题是大多数Windows系统错误消息在末尾都有回车符+换行符。将消息插入到其他文本中会出现问题,并且会破坏C ++异常消息的约定。所以,最好删除那些字符。

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

如果公开或移出类,ErrorMessage函数将更有用,并且按值获取错误代码,而不是使用指针参数。这是将单独责任分开的原则。促进重用。

如果使用析构函数释放内存,ErrorMessage中的代码将更加清晰,安全和高效。然后你也可以直接在return语句中构造字符串,而不是使用带有后插入器的复制循环。

干杯&amp;第h。,

答案 4 :(得分: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)");
    }
}

也许这对你们中的一些人有用。

答案 5 :(得分: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();
}

这将打印:“无法写入文件:访问被拒绝”