windows C++-winrt的错误处理(下)

在创作 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)在你使用标准模板库时引发异常的情况下受支持。 

使用 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 运行时方法,还是用来在前台和后台线程之间切换。 在这种情况下,“发后不理”方法仍可使用,但效率不高。 可以改为执行类似下面的代码。

HRESULT abi() noexcept try
{
    // ABI code goes here.
} catch (...) { winrt::terminate(); }
快速失败

上一部分的代码仍会快速失败。 从编写的内容来看,该代码不处理任何异常。 任何未经处理的异常都会导致程序终止。

但该形式是很好的,因为它确保了可调试性。 在罕见情况下,可能需要使用 try/catch,并处理某些异常。 但这应该很罕见,因为正如本主题所述,我们反对将异常作为一种流控制机制用于预期的条件。

记住,让未经处理的异常逃脱无包装的 noexcept 上下文是很糟糕的做法。 在该条件下,C++ 运行时会 std::terminate 进程,因此会失去任何存放的由 C++/WinRT 仔细记录的异常信息。

 断言

对应用程序中的内部假设,存在断言。 最好尽可能地为编译时验证使用“static_assert”。 对于运行时条件,请使用带布尔值表达式的 WINRT_ASSERT。 WINRT_ASSERT 是宏定义,并且扩展到 _ASSERTE。

WINRT_ASSERT(pos < size());

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

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

WINRT_VERIFY(::CloseHandle(value));
WINRT_VERIFY_(TRUE, ::CloseHandle(value));
  • 31
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值