将 GetLastError() 转换为异常

发布于 2024-10-08 01:23:44 字数 1886 浏览 0 评论 0原文

我有一个 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

该类在它所使用的情况下运行良好。我想知道是否有任何明显的情况会失败,我应该注意。欢迎任何其他问题、警告或一般性改进建议。

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

I have a Visual Studio 2008 C++ project that uses a Win32Exception class in cases where there is an exceptional error. The Win32Exception class looks like this:

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

The class works well in the situations it has been used in. What I would like to know is if there are any obvious cases where this will fail that I should be aware of. Any other gotchas, caveats, or general suggestions on improvements are welcome.

Please note that the boost library is not an option for this code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

讽刺将军 2024-10-15 01:23:44

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

Note that if the back_inserter causes std::bad_alloc to be thrown, the memory allocated inside FormatMessage is leaked.

女皇必胜 2024-10-15 01:23:44
  • 真是巧合啊!我在所有项目中都使用类似的代码!这实际上是一个好主意。

  • 这段代码有问题:

     // 这可能会生成 C4244 警告。忽略它是安全的。
        std::copy( error_messageW, 
                   error_messageW + len, 
                   std::back_inserter( error_messageA ) );
    

    它只是将 WCHAR 转换为字符。您可以显式使用 FormatMessageA 来获取当前代码页中的消息(好吧,您不能像您所说的那样),或者约定所有字符串均采用 UTF-8 编码。我选择了后者,请参阅这个原因。

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

  • What a coincidence! I use a similar code in all my projects! It is actually a good idea.

  • This code is problematic:

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

    It just trancates WCHARs to chars. Your can either use FormatMessageA explicitly to get a message in the current code-page (ok, you can't as you said), or make convention that all your stings are UTF-8 encoded. I chose the later, see this why.

  • Error message by itself may be not useful. Capturing the stack trace may be a good idea.

jJeQQOZ5 2024-10-15 01:23:44

意识到这已经过时了,但至少在 VC++ 2015 中,您可以抛出一个 system_error ,它将使用 system_category() 函数完成所有这些操作:

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

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

Realize this is old, but at least with VC++ 2015 you can throw a system_error that will do all this with the system_category() function:

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

This would print: "Failed to write file: Access is denied"

删除→记忆 2024-10-15 01:23:44
  • FormatMessage 本身可能会失败。对于这种情况,可能需要一些中性的“代码为 %d 的未知错误”。
  • 有些错误代码并不是真正的错误(ERROR_ALREADY_EXISTS),具体取决于用户的期望。
  • 某些系统函数返回其自己的错误代码(值得注意的示例是 SHFileOperation),您必须单独处理它们。如果您希望处理它们,那就是。
  • 考虑在异常中包含附加信息:异常从哪里抛出(源文件和行),哪个系统函数导致异常,函数的参数是什么(至少是标识参数,如文件名、句柄值或一些此类参数) )。堆栈跟踪也很好。
  • FormatMessage may itself fail. Some neutral "Unknown error with code %d" might be in order for such case.
  • Some error codes are not really errors (ERROR_ALREADY_EXISTS), depending on what user is expecting.
  • Some system functions return their own error codes (notable example being SHFileOperation) that you must handle separately. If you want them to be handled, that is.
  • Consider having additional information inside exception: where is exception being thrown from (source file and line), what system function caused exception, what were the parameters of the function (at least the identifying ones, like file name, handle value, or some such). Stack trace is also good.
悲欢浪云 2024-10-15 01:23:44

我想知道是否有
是否有任何明显的情况会导致
失败是我应该意识到的。任何
其他陷阱、警告或一般情况
改进建议是
欢迎。

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

我想这是辩证法所指出的一个特例,即“某些错误代码并不是真正的错误”,但对于大多数代码来说,至少该消息通常是可以接受的。

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

现在,不再重复其他人已经注意到的所有内容,而是介绍一下设计。

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

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

干杯&呵呵,

What I would like to know is if there
are any obvious cases where this will
fail that I should be aware of. Any
other gotchas, caveats, or general
suggestions on improvements are
welcome.

The main I've problem I've had with such message retrieval has been ERROR_SUCCESS. It's rather perplexing when some operation fails, accompanied by error message "The operation succeeded". One wouldn't think that could happen, but it does.

I guess this is a special case of what Dialecticus noted, that "Some error codes are not really errors", but for most of those codes at least the message is generally acceptable.

The second problem is that most Windows system error message have a carriage return + linefeed at the end. It's problematic for insertion of messages into other text, and it breaks the convention for C++ exception messages. So, good idea to remove those chars.

Now, instead of repeating all that others have already noted, a few words about the design.

The ErrorMessage function would much more usable if was made public or moved out of the class, and took the error code by value, instead of taking pointer argument. This is the principle of keeping separate responsibilities separate. Promotes reuse.

The code in ErrorMessage would be more clear and safe and efficient if you used a destructor to deallocate the memory. Then you could also just construct the string directly in the return statement instead of using a copy loop with back inserter.

Cheers & hth.,

像你 2024-10-15 01:23:44

我最近正在研究一个非常相似的类,在阅读此线程后尝试使复制部分异常安全。我引入了一个小帮助器类,它除了保存指向 ::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)");
    }
}

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

I was recently working on a very similar class and after reading this thread tried to make the copying part exception-safe. I introduced a little helper class that does nothing but hold the pointer to the string returned by ::FormatMessage and free it with ::LocalFree in its destructor. Copying, assigning and moving is not allowed, so one cannot get into trouble.

Here is what I came up with in 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)");
    }
}

Maybe this will be useful for some of you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文