C++/WinRT教程(第四篇)WinRT 的错误和异常处理

2024-03-02 02:04

本文主要是介绍C++/WinRT教程(第四篇)WinRT 的错误和异常处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

避免捕获和抛出异常

捕获异常

抛出异常

编辑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_ASSERTWINRT_ASSERT 是宏定义,并且扩展到 _ASSERTE。

WINRT_ASSERT(pos < size());

 WINRT_ASSERT 在发布版本中被编译; 在调试版本中,它会在断言所在的代码行上停止调试器中的应用程序。

 不应在析构函数中使用异常。 因此,至少在调试版本中,你可以断言从带有 WINRT_VERIFY(带有布尔值表达式)和 WINRT_VERIFY_(带有预期结果和布尔值表达式)的析构函数调用函数的结果。

WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));

这篇关于C++/WinRT教程(第四篇)WinRT 的错误和异常处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/764575

相关文章

VSCode中C/C++编码乱码问题的两种解决方法

《VSCode中C/C++编码乱码问题的两种解决方法》在中国地区,Windows系统中的cmd和PowerShell默认编码是GBK,但VSCode默认使用UTF-8编码,这种编码不一致会导致在VSC... 目录问题方法一:通过 Code Runner 插件调整编码配置步骤方法二:在 PowerShell

C/C++随机数生成的五种方法

《C/C++随机数生成的五种方法》C++作为一种古老的编程语言,其随机数生成的方法已经经历了多次的变革,早期的C++版本使用的是rand()函数和RAND_MAX常量,这种方法虽然简单,但并不总是提供... 目录C/C++ 随机数生成方法1. 使用 rand() 和 srand()2. 使用 <random

Win32下C++实现快速获取硬盘分区信息

《Win32下C++实现快速获取硬盘分区信息》这篇文章主要为大家详细介绍了Win32下C++如何实现快速获取硬盘分区信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实现代码CDiskDriveUtils.h#pragma once #include <wtypesbase

Java捕获ThreadPoolExecutor内部线程异常的四种方法

《Java捕获ThreadPoolExecutor内部线程异常的四种方法》这篇文章主要为大家详细介绍了Java捕获ThreadPoolExecutor内部线程异常的四种方法,文中的示例代码讲解详细,感... 目录方案 1方案 2方案 3方案 4结论方案 1使用 execute + try-catch 记录

Linux搭建Mysql主从同步的教程

《Linux搭建Mysql主从同步的教程》:本文主要介绍Linux搭建Mysql主从同步的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux搭建mysql主从同步1.启动mysql服务2.修改Mysql主库配置文件/etc/my.cnf3.重启主库my

SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法

《SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法》本文主要介绍了SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录方法1:更改IDE配置方法2:在Eclipse中清理项目方法3:使用Maven命令行在开发Sprin

SpringBoot操作MaxComputer方式(保姆级教程)

《SpringBoot操作MaxComputer方式(保姆级教程)》:本文主要介绍SpringBoot操作MaxComputer方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的... 目录引言uqNqjoe一、引入依赖二、配置文件 application.properties(信息用自己

Tomcat的下载安装与使用教程

《Tomcat的下载安装与使用教程》本文介绍了Tomcat的下载、安装和使用方法,包括在本机和云服务器上部署Tomcat的过程,以及解决启动失败问题的方法... 目录Tomcat的下载安装与使用Tomcat的下载与安装Tomcat在本机运行使用Tomcat在php云服务器上的使用总结Tomcat的下载安装与

SpringBoot基于沙箱环境实现支付宝支付教程

《SpringBoot基于沙箱环境实现支付宝支付教程》本文介绍了如何使用支付宝沙箱环境进行开发测试,包括沙箱环境的介绍、准备步骤、在SpringBoot项目中结合支付宝沙箱进行支付接口的实现与测试... 目录一、支付宝沙箱环境介绍二、沙箱环境准备2.1 注册入驻支付宝开放平台2.2 配置沙箱环境2.3 沙箱

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值