Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cette rubrique décrit les stratégies de gestion des erreurs lors de la programmation avec C++/WinRT. Pour plus d’informations générales et d’arrière-plan, consultez Erreurs et gestion des exceptions (C++moderne).
Éviter d’intercepter et de lever des exceptions
Nous vous recommandons de continuer à écrire du code sans risque d’exception, mais vous préférez éviter d’intercepter et de lever des exceptions dans la mesure du possible. S'il n'existe aucun gestionnaire pour une exception, Windows génère automatiquement un rapport d'erreur (y compris un minidump de l'incident), ce qui vous aidera à suivre l'emplacement où se trouve le problème.
Ne lèvez pas d’exception que vous vous attendez à intercepter. Et n’utilisez pas d’exceptions pour les échecs attendus. Lèvez une exception uniquement lorsqu’une erreur d’exécution inattendue se produit et gérez tout le reste avec des codes d’erreur/résultat, directement et près de la source de l’échec. De cette façon, lorsqu’une exception est levée, vous savez que la cause est un bogue dans votre code ou un état d’erreur exceptionnel dans le système.
Envisagez le scénario d’accès au Registre Windows. Si votre application ne parvient pas à lire une valeur dans le Registre, c'est normal, et vous devez gérer cette situation correctement. Ne levez pas d’exception ; retournez plutôt une valeur bool ou enum indiquant que la valeur n’a pas été lue et, éventuellement, pourquoi. En revanche, si l’écriture d’une valeur dans le Registre échoue, cela indique probablement un problème plus important que ce que votre application peut raisonnablement gérer. Dans un cas comme celui-ci, vous ne souhaitez pas que votre application continue, de sorte qu’une exception qui entraîne un rapport d’erreurs est le moyen le plus rapide pour empêcher votre application d’entraîner des dommages.
Pour un autre exemple, envisagez de récupérer une image miniature à partir d’un appel à StorageFile.GetThumbnailAsync, puis de passer cette miniature à BitmapSource.SetSourceAsync. Si cette séquence d’appels vous amène à passer nullptr à SetSourceAsync (le fichier image ne peut pas être lu ; peut-être que son extension de fichier donne à penser qu’il contient des données d’image, alors qu’en réalité ce n’est pas le cas), vous provoquerez le déclenchement d’une exception de pointeur non valide. Si vous découvrez un cas de ce type dans votre code, au lieu d’intercepter et de traiter ce cas comme une exception, vérifiez plutôt si GetThumbnailAsync renvoie nullptr.
La levée d’exceptions a tendance à être plus lente que l’utilisation de codes d’erreur. Si vous ne lancez une exception qu’en cas d’erreur fatale, alors, si tout se passe bien, vous n’aurez jamais à payer le coût en performances.
Toutefois, une baisse de performances plus probable provient du surcoût à l’exécution lié au fait de garantir que les destructeurs appropriés sont appelés dans l’éventualité peu probable où une exception est levée. Le coût de cette garantie est encouru, qu’une exception soit réellement levée ou non. Par conséquent, vous devez vous assurer que le compilateur a une bonne idée de ce que les fonctions peuvent potentiellement lever des exceptions. Si le compilateur peut prouver qu’il n’y aura aucune exception de certaines fonctions (spécification noexcept ), il peut optimiser le code qu’il génère.
Gestion des exceptions
Une condition d’erreur qui se produit au niveau de la couche ABI Windows Runtime est retournée sous la forme d’une valeur HRESULT. Mais vous n’avez pas besoin de gérer les HRESULT dans votre code. Le code de projection C++/WinRT généré pour une API côté consommateur détecte un code HRESULT d’erreur au niveau de la couche ABI et convertit le code en exception winrt ::hresult_error , que vous pouvez intercepter et gérer. Si vous souhaitez gérer les HRESULT, utilisez le type winrt ::hresult .
Par exemple, si l’utilisateur vient à supprimer une image de la bibliothèque Images pendant que votre application parcourt cette collection, la projection lève une exception. Et c’est un cas où vous devrez intercepter et gérer cette exception. Voici un exemple de code montrant ce cas.
#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.
}
}
}
Utilisez ce même modèle dans une coroutine lors de l’appel d’une fonction co_await. Un autre exemple de cette conversion de HRESULT en exception est que, lorsqu’une API d’un composant renvoie E_OUTOFMEMORY, cela provoque la levée d’une exception std::bad_alloc.
Préférez winrt ::hresult_error ::code lorsque vous examinez simplement un code HRESULT. La fonction winrt ::hresult_error ::to_abi en revanche se convertit en objet d’erreur COM et envoie (push) l’état dans le stockage local du thread COM.
Lancer des exceptions
Il existe des cas où vous décidez que, si votre appel à une fonction donnée échoue, votre application ne pourra pas récupérer (vous ne serez plus en mesure de l’utiliser pour fonctionner de façon prévisible). L’exemple de code ci-dessous utilise une valeur winrt ::handle comme wrapper autour du HANDLE retourné par CreateEvent. Il transmet ensuite le handle (en créant une valeur bool à partir de celui-ci) au modèle de fonction winrt::check_bool.
winrt ::check_bool fonctionne avec un bool, ou avec une valeur convertible en false (condition d’erreur) ou true (condition de réussite).
winrt::handle h{ ::CreateEvent(nullptr, false, false, nullptr) };
winrt::check_bool(bool{ h });
winrt::check_bool(::SetEvent(h.get()));
Si la valeur que vous passez à winrt ::check_bool a la valeur false, la séquence d’actions suivante a lieu.
- winrt ::check_bool appelle la fonction winrt ::throw_last_error .
- winrt ::throw_last_error appelle GetLastError pour récupérer la dernière valeur de code d’erreur du thread appelant, puis appelle la fonction winrt ::throw_hresult .
- winrt ::throw_hresult lève une exception à l’aide d’un objet winrt ::hresult_error (ou d’un objet standard) qui représente ce code d’erreur.
Étant donné que Windows API signalent des erreurs d’exécution à l’aide de différents types de valeur de retour, il existe en plus de winrt ::check_bool une poignée d’autres fonctions d’assistance utiles pour vérifier les valeurs et lever des exceptions.
- winrt ::check_hresult. Vérifie si le code HRESULT représente une erreur et, le cas échéant, appelle winrt ::throw_hresult.
- winrt ::check_nt. Vérifie si un code représente une erreur et, le cas échéant, appelle winrt ::throw_hresult.
- winrt::check_pointer. Vérifie si un pointeur a la valeur Null et, le cas échéant, appelle winrt ::throw_last_error.
- winrt ::check_win32. Vérifie si un code représente une erreur et, le cas échéant, appelle winrt ::throw_hresult.
Vous pouvez utiliser ces fonctions d’assistance pour les types de code de retour courants, ou vous pouvez répondre à n’importe quelle condition d’erreur et appeler winrt ::throw_last_error ou winrt ::throw_hresult.
Lever des exceptions lors de la conception d’une API
Toutes les limites de l’interface binaire d’application de Windows Runtime (ou limites ABI) doivent être noexcept — ce qui signifie que les exceptions ne doivent jamais s’en échapper. Lorsque vous créez une API, vous devez toujours marquer la limite ABI avec le mot clé C++ noexcept .
noexcept a un comportement spécifique en C++. Si une exception C++ atteint une noexcept limite, le processus échoue rapidement avec std ::terminate. Ce comportement est généralement souhaitable, car une exception non gérée implique presque toujours un état inconnu dans le processus.
Étant donné que les exceptions ne doivent pas franchir la limite ABI, une condition d’erreur qui se produit dans une implémentation est retournée dans la couche ABI sous la forme d’un code d’erreur HRESULT. Lorsque vous créez une API à l’aide de C++/WinRT, le code est généré pour vous permettre de convertir toute exception que vous lèvez dans votre implémentation en HRESULT. La fonction winrt ::to_hresult est utilisée dans ce code généré dans un modèle comme celui-ci.
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 gère les exceptions dérivées de std ::exception et winrt ::hresult_error et ses types dérivés. Dans votre implémentation, vous devez préférer winrt ::hresult_error, ou un type dérivé, afin que les consommateurs de votre API reçoivent des informations d’erreur enrichies. std ::exception (qui est mappée à E_FAIL) est prise en charge si des exceptions proviennent de votre utilisation de la bibliothèque de modèles standard.
Facilité de débogage avec noexcept
Comme nous l’avons mentionné ci-dessus, une exception C++ qui atteint une noexcept limite échoue rapidement avec std ::terminate. Ce n’est pas idéal pour le débogage, car std::terminate fait souvent perdre une grande partie, voire la totalité, du message d’erreur ou du contexte de l’exception levée, en particulier lorsque des coroutines entrent en jeu.
Par conséquent, cette section traite du cas où votre méthode ABI (que vous avez correctement annotée avec noexcept) utilise co_await pour appeler le code de projection C++/WinRT asynchrone. Nous vous recommandons d’encapsuler les appels au code de projection C++/WinRT dans un winrt ::fire_and_forget. Cela fournit l’emplacement approprié pour enregistrer correctement une exception non gérée en tant qu’exception stockée, ce qui améliore considérablement la facilité de débogage.
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 a un helper de méthode intégré unhandled_exception , qui appelle winrt ::terminate, qui appelle à son tour RoFailFastWithErrorContext. Cela garantit que tout contexte (exception stockée, code d’erreur, message d’erreur, trace de la pile d’appels, etc.) est conservé, soit pour le débogage en direct, soit pour un vidage mémoire post-mortem. Pour plus de commodité, vous pouvez factoriser la partie fire-and-forget dans une fonction distincte qui retourne un winrt ::fire_and_forget, puis appeler cela.
Code synchrone
Dans certains cas, votre méthode ABI (qui, à nouveau, vous avez correctement annoté avec noexcept) appelle uniquement du code synchrone. En d’autres termes, il n’utilise co_awaitjamais , soit pour appeler une méthode de Windows Runtime asynchrone, soit pour basculer entre les threads de premier plan et d’arrière-plan. Dans ce cas, la technique fire_and_forget fonctionnera toujours, mais elle n’est pas optimale. Au lieu de cela, vous pouvez faire quelque chose comme ça.
HRESULT abi() noexcept try
{
// ABI code goes here.
} catch (...) { winrt::terminate(); }
Échouer rapidement
Le code de la section précédente échoue toujours rapidement. Comme écrit, ce code ne gère aucune exception. Toute exception non gérée entraîne l’arrêt du programme.
Mais cette forme est meilleure, car elle assure la facilité de débogage. Dans de rares cas, vous souhaiterez peut-être try/catch, et gérer certaines exceptions. Mais cela devrait être rare, car, comme l’explique cette rubrique, nous déconseillons d’utiliser des exceptions comme mécanisme de contrôle de flux pour les conditions attendues.
N’oubliez pas que c’est une mauvaise idée de laisser une exception non gérée échapper à un contexte nu noexcept . Dans ce cas, l’environnement d’exécution C++ appellera le processus std::terminate, entraînant ainsi la perte de toutes les informations sur les exceptions stockées que C++/WinRT a soigneusement enregistrées.
Assertions
Pour les hypothèses internes dans votre application, il existe des assertions. Préférez static_assert pour la validation au moment de la compilation, dans la mesure du possible. Pour les conditions d’exécution, utilisez WINRT_ASSERT une expression booléenne.
WINRT_ASSERT est une définition de macro, et elle s’étend à _ASSERTE.
WINRT_ASSERT(pos < size());
WINRT_ASSERT est supprimée à la compilation dans les versions release ; dans une version de débogage, elle interrompt l’application dans le débogueur à la ligne de code contenant l’assertion.
Vous ne devez pas utiliser d’exceptions dans vos destructeurs. Ainsi, au moins dans les versions de débogage, vous pouvez vérifier le résultat de l’appel d’une fonction depuis un destructeur avec WINRT_VERIFY (avec une expression booléenne) et WINRT_VERIFY_ (avec un résultat attendu et une expression booléenne).
WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));
API importantes
- modèle de fonction winrt ::check_bool
- fonction winrt::check_hresult
- modèle de fonction winrt ::check_nt
- modèle de fonction winrt ::check_pointer
- modèle de fonction winrt ::check_win32
- winrt ::handle struct
- struct winrt ::hresult_error
- fonction winrt::throw_hresult
- fonction winrt::throw_last_error
- fonction winrt::to_hresult
Rubriques connexes
Windows developer