Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ez a témakör a C++/WinRT-vel való programozás során felmerülő hibák kezelésére vonatkozó stratégiákat ismerteti. További általános információ és háttér: Hibák és kivételkezelés (Modern C++).
Kerülje a kivételeket a fogás és a dobás elkerülése érdekében
Azt javasoljuk, hogy továbbra is írjon kivételbiztos kódot, de lehetőség szerint kerülje a kivételek elkapását és dobását. Ha nincs kezelője a kivételnek, akkor Windows automatikusan létrehoz egy hibajelentést (beleértve az összeomlás minidumpát is), amely segít a probléma azonosításában.
Ne váltson ki olyan kivételt, amelyről arra számít, hogy elkapja. Ne használjon kivételeket a várt hibákhoz. Kivételt csak váratlan futásidejű hiba esetén kell kivenni, és minden mást hiba-/eredménykódokkal kezelni – közvetlenül és a hiba forrásához közel. Így, ha kivételt okoz, akkor tudja, hogy az ok vagy egy hiba a kódban, vagy egy kivételes hibaállapot a rendszerben.
Fontolja meg a Windows beállításjegyzék elérésének forgatókönyvét. Ha az alkalmazás nem tud beolvasni egy értéket a beállításjegyzékből, akkor ez várható, és ezt elegánsan kell kezelnie. Ne dobjon kivételt; inkább egy bool vagy enum értékkel térjen vissza, amely jelzi, hogy az értéket nem sikerült beolvasni, és esetleg azt is, miért. Az, hogy nem sikerül értéket írni a Beállításjegyzékbe, viszont valószínűleg arra utal, hogy a probléma nagyobb annál, mint amit észszerűen kezelni tud az alkalmazáson belül. Ilyen esetben nem szeretné, hogy az alkalmazás folytatódjon, ezért a hibajelentést eredményező kivétel a leggyorsabb módja annak, hogy az alkalmazás ne okozz kárt.
Egy másik példában fontolja meg egy miniatűr kép lekérését a StorageFile.GetThumbnailAsync hívásából, majd adja át a miniatűrt a BitmapSource.SetSourceAsyncnek. Ha a hívásoknak ez a sorozata azt eredményezi, hogy a SetSourceAsync metódusnak a nullptr értéket adja át (a képfájl nem olvasható; lehet, hogy a fájlkiterjesztése alapján úgy tűnik, mintha képadatokat tartalmazna, de valójában nem), akkor érvénytelen mutató miatti kivétel keletkezik. Ha ilyen esettel találkozik a kódjában, ahelyett, hogy kivételként elkapná és kezelné, inkább ellenőrizze, hogy a GetThumbnailAsyncnullptr értéket adott-e vissza.
A kivételek eldobása általában lassabb, mint a hibakódok használata. Ha csak akkor ad kivételt, ha végzetes hiba történik, akkor ha minden jól megy, soha nem fogja kifizetni a teljesítmény árát.
A nagyobb valószínűséggel járó teljesítménycsökkenés azonban magában foglalja a futásidejű többletterhelést, amely biztosítja, hogy a megfelelő destruktorokat meghívják abban a valószínűtlen esetben, amikor kivétel történik. Ennek a garanciának a költsége akkor is jelentkezik, ha ténylegesen történik kivétel, és akkor is, ha nem. Ezért gondoskodnia kell arról, hogy a fordító jól tudja, milyen függvények okozhatnak kivételeket. Ha a fordító bizonyítani tudja, hogy bizonyos függvények (specifikáció noexcept ) alól nem lesz kivétel, akkor optimalizálhatja a létrehozott kódot.
Kivételek elfogása
A Windows-futtatókörnyezet ABI-rétegben felmerülő hibafeltételek HRESULT érték formájában lesznek visszaadva. De nem kell kezelnie a HRESULT-eket a kódban. A fogyasztó oldalon egy API-hoz létrehozott C++/WinRT-vetítési kód észleli a HRESULT-kódot az ABI-rétegben, és winrt::hresult_error kivételré alakítja a kódot, amelyet elkaphat és kezelhet. Ha szeretné kezelni a HRESULT-eket, használja a winrt::hresult típust .
Ha például a felhasználó éppen töröl egy képet a Képek tárból, miközben az alkalmazás iterálja a gyűjteményt, akkor a vetítés kivételt vetít ki. És ez egy olyan eset, amikor el kell kapnia és kezelnie kell ezt a kivételt. Íme egy példa erre az esetre.
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Microsoft::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.
}
}
}
Használja ugyanezt a mintát egy korutinban a co_await-tel ellátott függvény meghívásakor. Egy másik példa erre a HRESULT kivétellé alakítására az, hogy ha egy összetevő API egy E_OUTOFMEMORY értéket ad vissza, akkor std::bad_alloc kivétel dobódik.
Használja inkább a winrt::hresult_error::code függvényt, ha csak bele szeretne nézni egy HRESULT-kódba. A winrt::hresult_error::to_abi függvény viszont COM-hibaobjektummá alakul át, és az állapotot leküldi a COM szál helyi tárolójába.
Kivételek kivetése
Vannak olyan esetek, amikor úgy dönt, hogy ha egy adott függvény hívása meghiúsul, az alkalmazás nem fog tudni helyreállni (a továbbiakban nem fog tudni arra támaszkodni, hogy kiszámíthatóan működjön). Az alábbi kód példája egy winrt::handle értéket használ burkolóként a CreateEventből visszaadott HANDLE körül. Ezután átadja a kezelőt — bool értéket hozva létre belőle — a winrt::check_bool függvénysablonnak. A winrt::check_bool használható egy bool típussal, illetve bármely olyan értékkel, amely false típussá (hibafeltétel), vagy true típussá (sikeres állapot) alakítható.
winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));
Ha a winrt::check_bool érték hamis, akkor a következő műveletek sorozata történik.
- winrt::check_bool meghívja a winrt::throw_last_error függvényt.
- winrt::throw_last_error meghívja a GetLastErrort a híváslánc utolsó hibakódértékének lekéréséhez, majd meghívja a winrt::throw_hresult függvényt.
- winrt::throw_hresult kivételt jelez egy winrt::hresult_error objektummal (vagy egy standard objektummal), amely az adott hibakódot jelöli.
Mivel a Windows API-k különféle visszatérésiérték-típusokkal jelzik a futásidejű hibákat, a winrt::check_bool mellett még néhány más hasznos segédfüggvény is rendelkezésre áll az értékek ellenőrzésére és kivételek dobására.
- winrt::check_hresult. Ellenőrzi, hogy a HRESULT-kód hibát jelez-e, és ha igen, meghívja a winrt::throw_hresult.
- winrt::check_nt. Ellenőrzi, hogy egy kód hibát jelez-e, és ha igen, meghívja a winrt::throw_hresult.
- winrt::check_pointer. Ellenőrzi, hogy egy mutató null értékű-e, és ha igen, meghívja a winrt::throw_last_error.
- winrt::check_win32. Ellenőrzi, hogy egy kód hibát jelez-e, és ha igen, meghívja a winrt::throw_hresult.
Ezeket a segédfüggvényeket használhatja a gyakori visszatérési kódtípusokhoz, vagy válaszolhat bármilyen hibaállapotra, és meghívhatja a winrt::throw_last_error vagy a winrt::throw_hresult parancsot.
Kivételek kivetése API létrehozásakor
Az alkalmazás bináris felületének minden Windows-futtatókörnyezet határának (vagy ABI-határának) noexceptnek kell lennie, ami azt jelenti, hogy a kivételeknek ott soha nem szabad kikerülniük. API létrehozásakor mindig a C++ noexcept kulcsszóval kell megjelölnie az ABI-határt.
noexcept A C++-ban meghatározott viselkedést ad meg. Ha egy C++ kivétel eléri a határt noexcept , akkor a folyamat gyorsan meghiúsul az std::terminate billentyűkombinációval. Ez a viselkedés általában kívánatos, mivel a kezeletlen kivétel szinte mindig ismeretlen állapotot jelent a folyamatban.
Mivel a kivételeknek nem szabad átlépniük az ABI-határt, a megvalósítás során felmerülő hibafeltételek HRESULT hibakód formájában lesznek visszaadva az ABI-rétegben. Amikor C++/WinRT használatával hoz létre API-t, a rendszer létrehoz egy kódot, amellyel az implementációban keletkező kivételeket HRESULT-lá konvertálja. A winrt::to_hresult függvény az ilyen mintában létrehozott kódban használatos.
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 kezeli az std::exception és a winrt::hresult_error és származtatott típusaiból származó kivételeket. Az implementációban inkább a winrt::hresult_error vagy származtatott típust érdemes használnia, hogy az API felhasználói részletes hibainformációkat kapjanak. std::exception (amely az E_FAIL értékhez van leképezve) támogatott, ha a Standard Template Library (STL) használata során kivételek merülnek fel.
Hibakereshetőség noexcept használatával
Ahogy fentebb említettük, ha egy C++ kivétel noexcept határba ütközik, a program azonnal leáll az std::terminate hívásával. Ez nem ideális hibakereséshez, mert az std::terminate gyakran elveszíti a hiba vagy a kivétel kontextusának nagy részét vagy egészét, különösen akkor, ha a koroutinok érintettek.
Ez a szakasz tehát azzal az esettel foglalkozik, amikor a megfelelően noexcept elemmel annotált ABI-metódusa a(z) co_await használatával aszinkron C++/WinRT-projekciós kódot hív meg. Javasoljuk, hogy a C++/WinRT-vetítési kód hívásait egy winrt::fire_and_forget belül csomagolja be. Ez megfelelő helyet biztosít arra, hogy a kezeletlen kivétel megfelelően félretett kivételként legyen rögzítve, ami jelentősen javítja a hibakereshetőséget.
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 rendelkezik egy beépített unhandled_exception metódussegítővel, amely meghívja a winrt::terminate parancsot, amely viszont meghívja a RoFailFastWithErrorContext parancsot. Ez garantálja, hogy minden környezet (kivédett kivétel, hibakód, hibaüzenet, stack backtrace stb.) megmaradjon élő hibakereséshez vagy a mortem utáni memóriaképhez. A kényelem kedvéért a fire-and-forget részt kiszervezheti egy külön függvénybe, amely winrt::fire_and_forget értéket ad vissza, majd azt hívhatja meg.
Szinkron kód
Bizonyos esetekben az ABI-metódusod (amelyet, mint említettük, megfelelően elláttál a noexcept jelöléssel) csak szinkron kódot hív meg. Más szóval soha nem használja co_awaitaz aszinkron Windows-futtatókörnyezet metódus meghívását, vagy az előtér- és háttérszálak közötti váltást. Ebben az esetben a fire_and_forget technika továbbra is működik, de nem hatékony. Ehelyett valami ilyesmit tehet.
HRESULT abi() noexcept try
{
// ABI code goes here.
} catch (...) { winrt::terminate(); }
Gyors hibafelismerés
Az előző szakaszban szereplő kód továbbra is gyorsan meghiúsul. Ahogy megírtuk, ez a kód nem kezeli a kivételeket. A kezeletlen kivétel programkizárást eredményez.
De az a forma jobb, mert biztosítja a hibakereshetőséget. Ritka esetekben előfordulhat, hogy szeretné try/catch, és kezelni bizonyos kivételeket. Ennek azonban ritkanak kell lennie, mert a jelen témakörben leírtak szerint a kivételek nem használhatók folyamatvezérlési mechanizmusként a várt feltételekhez.
Ne feledje, hogy rossz ötlet hagyni, hogy egy kezeletlen kivétel kijusson egy csupasz noexcept kontextusból. Ebben a feltételben a C++ futtatókörnyezet std::leállítja a folyamatot, ezáltal elveszíti a C++/WinRT által gondosan rögzített rögzített kivételadatokat.
Assertions
Az alkalmazás belső feltételezéseihez vannak állítások. Ahol csak lehetséges, részesítsd előnyben a static_assert használatát a fordítási idejű ellenőrzéshez. Futásidejű feltételek esetén logikai kifejezéssel használható WINRT_ASSERT .
WINRT_ASSERT egy makródefiníció, és _ASSERTE kifejezésre bontódik ki.
WINRT_ASSERT(pos < size());
A WINRT_ASSERT a kiadási buildből kimarad; hibakeresési buildben viszont leállítja az alkalmazást a hibakeresőben azon a kódsoron, ahol az assert szerepel.
Ne használjon kivételeket a destruktorokban. Tehát legalább a hibakeresési összeállításokban ellenőrizheti egy függvény destruktorból történő meghívásának eredményét a WINRT_VERIFY (logikai kifejezéssel), illetve a WINRT_VERIFY_ (elvárt eredménnyel és logikai kifejezéssel) használatával.
WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));
Fontos API-k
- winrt::check_bool függvénysablon
- winrt::check_hresult függvény
- winrt::check_nt függvénysablon
- winrt::check_pointer függvénysablon
- winrt::check_win32 függvénysablon
- winrt::handle struct
- winrt::hresult_error struct
- winrt::throw_hresult függvény
- winrt::throw_last_error függvény
- winrt::to_hresult függvény
Kapcsolódó témakörök
Windows developer