2010-12-17 91 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(); 
} 

這將打印:「無法寫入文件:訪問是否認「

相關問題