目錄
前言
避免捕獲和拋出異常
捕獲異常
拋出異常
編輯API時拋出異常
使用 noexcept 時如何調試
調用同步代碼
快速失敗
斷言
前言
本文主要介紹?C++/WinRT?中的異常如何使用以及使用原則,如果你剛開始接觸WinRT,建議先閱讀第一篇。
C++/WinRT教程(第一篇)-CSDN博客
C++/WinRT教程(第二篇)基礎類型的使用-CSDN博客
C++/WinRT教程(第三篇)API的使用-CSDN博客
其他資料:
現代 C++ 處理異常和錯誤的最佳做法 | Microsoft Learn
操作說明:異常安全性設計 | Microsoft Learn
避免捕獲和拋出異常
最好盡量避免捕獲和拋出異常。 如果沒有異常處理程序,Windows 將自動生成錯誤報告(包括故障的小型轉儲),以便跟蹤問題所在位置。
應僅在發生意外運行時錯誤時拋出異常,并處理帶有錯誤/結果代碼的任何其他事項,直接并靠近故障原因。 這樣,當異常“被”引發時,你會知道原因是代碼中的 bug 還是系統中的異常錯誤狀態。
例如訪問 Windows 注冊表的場景。 如果你的應用無法從注冊表讀取值,這是預料之中的,你應該正確處理。 不要拋出異常;而應返回?bool
?或?enum
?值指示未讀取值或原因。 另一方面,無法向注冊表寫入值很可能表示你的應用程序中存在的問題更大
拋出異常異常往往會比使用錯誤代碼更慢。谷歌和微軟代碼規范都不提倡使用異常。
捕獲異常
在?Windows 運行時 ABI?層出現的錯誤狀態以 HRESULT 值的形式返回。 不過你無需處理代碼中的 HRESULT。 為每個使用方的 API 生成的 C++/WinRT 投影代碼將檢測 ABI 層的錯誤 HRESULT 代碼,并將代碼轉換為你可以捕獲并處理的?winrt::hresult_error?異常。 如果你的確希望處理 HRESULTS,那么請使用“winrt::hresult”類型。
例如,如果用戶碰巧,在你的應用程序迭代圖片庫時,從該集合中刪除了圖像,那么投影將拋出異常。 這是你必須捕獲和處理該異常的一種情況。 下面的代碼示例展示了這種情況。
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Windows::UI::Xaml::Media::Imaging;IAsyncAction MakeThumbnailsAsync()
{auto imageFiles{ co_await KnownFolders::PicturesLibrary().GetFilesAsync() };for (StorageFile const& imageFile : imageFiles){BitmapImage bitmapImage;try{auto thumbnail{ co_await imageFile.GetThumbnailAsync(FileProperties::ThumbnailMode::PicturesView) };if (thumbnail) bitmapImage.SetSource(thumbnail);}catch (winrt::hresult_error const& ex){winrt::hresult hr = ex.code(); // HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).winrt::hstring message = ex.message(); // The system cannot find the file specified.}}
}
請在調用?co_await
?的函數時在協調程序中使用相同模式。 此 HRESULT 到異常轉換的另一個示例是,當組件 API 返回 E_OUTOFMEMORY 時,會導致拋出“std::bad_alloc”。
如果只是要瀏覽 HRESULT 代碼,則首選?winrt::hresult_error::code。 另一方面,winrt::hresult_error::to_abi?函數轉換為 COM 錯誤對象,并將狀態推送到 COM 線程本地存儲。
拋出異常
注:慎重,沒捕獲成功你的程序會原地崩潰
下方代碼示例使用?winrt::handle?值作為從?CreateEvent?返回的 HANDLE 的包裝 。 然后將該句柄(從其創建?bool
?值)傳遞到?winrt::check_bool?函數模板。
?“winrt::check_bool”使用?bool
?或任何可轉換為?false
(錯誤條件)或?true
(成功條件)的值。
winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));
如果你傳遞到?winrt::check_bool?的值為 false,那么以下操作序列將生效。
- “winrt::check_bool”調用?winrt::throw_last_error?函數 。
- “winrt::throw_last_error”調用?GetLastError?來檢索調用線程的最后一個錯誤代碼值,然后調用?winrt::throw_hresult?函數 。
- “winrt::throw_hresult”使用表示該錯誤代碼的?winrt::hresult_error?對象(或標準對象)拋出異常 。
由于 Windows API 使用各種返回值類型報告運行時的錯誤,因此除“winrt::check_bool”外,還有其他一些用于檢查值和拋出異常的有用的幫助程序函數。
- winrt::check_hresult。 檢查 HRESULT 代碼是否表示錯誤,如果是,則調用“winrt::throw_hresult”。
- winrt::check_nt。 檢查代碼是否表示錯誤,如果是,則調用“winrt::throw_hresult”。
- winrt::check_pointer。 檢查指針是否為 null,如果是,則調用“winrt::throw_last_error”。
- winrt::check_win32。 檢查代碼是否表示錯誤,如果是,則調用“winrt::throw_hresult”。
你可以對常見的返回代碼類型使用這些幫助程序函數,也可以響應任何錯誤條件并調用?winrt::throw_last_error?或?winrt::throw_hresult?。
編輯API時拋出異常
所有?Windows 運行時應用程序二進制接口邊界(簡稱 ABI 邊界)必須為 noexcept,即不得有異常。 創作 API 時,應始終使用 C++?noexcept
?關鍵字來標記 ABI 邊界。?noexcept
?在 C++ 中有特定的行為。 如果 C++ 異常遇到?noexcept
?邊界,則會調用?std::terminate,導致進程很快失敗。
該行為通常是理想的做法,因為未經處理的異常幾乎總是意味著進程中出現了未知狀態。
由于異常不得跨過 ABI 邊界,在實現中出現的錯誤條件以 HRESULT 錯誤代碼的形式跨 ABI 層返回。 在使用 C++/WinRT 創作 API 時,將生成代碼以供你將在實現中拋出的任何異常轉換為 HRESULT。?Winrt::to_hresult?函數以與此類似的模式用于生成的代碼。
HRESULT DoWork() noexcept
{try{// Shim through to your C++/WinRT implementation.return S_OK;}catch (...){return winrt::to_hresult(); // Convert any exception to an HRESULT.}
}
winrt::to_hresult?處理派生自 std::exception 和?winrt::hresult_error?及其派生類型的異常 。 在你的實現中,最好使用 winrt::hresult_error 或派生類型,以便你的 API 的使用者可以收到豐富的錯誤信息。 “std::exception”(映射到 E_FAIL)在你使用標準模板庫時引發異常的情況下受支持。
注:如果捕獲std::exception就捕獲不到?winrt::hresult_error?,所以最好就使用winrt::to_hresult?
使用 noexcept 時如何調試
如前所述,如果 C++ 異常遇到?noexcept
?邊界,則會調用?std::terminate,導致進程很快失敗。 這不適用于調試,因為?std::terminate?通常會失去引發的大部分或所有錯誤或異常上下文,尤其是在涉及協同程序的情況下。
因此,本部分處理的是 ABI 方法(已使用?noexcept
?進行適當的批注)使用?co_await
?來調用異步 C++/WinRT 投影代碼的情況。
建議將對 C++/WinRT 項目代碼的調用包裝在?winrt::fire_and_forget?中。 這樣做就可以在正確的位置將未經處理的異常正確記錄為存放異常,大大提高可調試性。
HRESULT MyWinRTObject::MyABI_Method() noexcept
{winrt::com_ptr<Foo> foo{ get_a_foo() };[/*no captures*/](winrt::com_ptr<Foo> foo) -> winrt::fire_and_forget{co_await winrt::resume_background();foo->ABICall();AnotherMethodWithLotsOfProjectionCalls();}(foo);return S_OK;
}
winrt::fire_and_forget?有內置的?unhandled_exception
?方法幫助程序,該程序調用?winrt::terminate,后者又調用?RoFailFastWithErrorContext。 這樣就可以保證任何上下文(存放異常、錯誤代碼、錯誤消息、堆棧回溯等)都會得到保存,不管是進行實時調試,還是進行事后轉儲。 為了方便起見,可以將“發后不理”部分重構成一個單獨的可返回?winrt::fire_and_forget?的函數,然后調用它。
調用同步代碼
在某些情況下,ABI 方法(同樣已使用?noexcept
?進行適當的批注)僅調用同步代碼。 換而言之,它從不使用?co_await
,不管是用來調用異步 Windows 運行時方法,還是用來在前臺和后臺線程之間切換。 在這種情況下,“?winrt::fire_and_forget”方法仍可使用,但效率不高。 可以改為執行類似下面的代碼。?
HRESULT abi() noexcept try
{// ABI code goes here.
} catch (...) { winrt::terminate(); }
快速失敗
上一部分的代碼仍會快速失敗。 從編寫的內容來看,該代碼不處理任何異常。 任何未經處理的異常都會導致程序終止。
但該形式是很好的,因為它確保了可調試性。 在罕見情況下,可能需要使用?try/catch
,并處理某些異常。 但這應該很罕見,因為正如本主題所述,我們反對將異常作為一種流控制機制用于預期的條件。
記住,讓未經處理的異常逃脫無包裝的?noexcept
?上下文是很糟糕的做法。 在該條件下,C++ 運行時會?std::terminate?進程,因此會失去任何存放的由 C++/WinRT 仔細記錄的異常信息。
斷言
對應用程序中的內部假設,存在斷言。 最好盡可能地為編譯時驗證使用“static_assert”。
WinRT使用帶布爾值表達式的?WINRT_ASSERT
。WINRT_ASSERT
?是宏定義,并且擴展到?_ASSERTE。
WINRT_ASSERT(pos < size());
?WINRT_ASSERT 在發布版本中被編譯; 在調試版本中,它會在斷言所在的代碼行上停止調試器中的應用程序。
?不應在析構函數中使用異常。 因此,至少在調試版本中,你可以斷言從帶有 WINRT_VERIFY(帶有布爾值表達式)和 WINRT_VERIFY_(帶有預期結果和布爾值表達式)的析構函數調用函數的結果。
WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));