在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分
摘要
系列文章第6部分:在C++/WinRT中创建敏捷的Windows Runtime委托,修复std::unique_ptr自定义删除器构造函数可能引发的异常问题。
<p>当我们<a title="在C++/WinRT中创建敏捷版的Windows Runtime委托,第5部分" href="https://devblogs.microsoft.com/oldnewthing/20260724-00/?p=112562">修复了在正确线程上释放不可封送委托的问题</a>后,看起来我们已经完成了。</p>
<p>但我们遗漏了一些东西。</p>
<p>又一次。</p>
<pre> if (d.try_as<::INoMarshal>()) {
void* p;
if constexpr (std::is_reference_v<Delegate>) {
p = winrt::detach_abi(d);
} else {
winrt::copy_to_abi(d, p);
}
return
[p = std::unique_ptr<void, in_context_deleter>(p),
token = get_context_token()](auto&&...args) {
if (token == get_context_token()) {
std::remove_reference_t<Delegate> d;
winrt::copy_from_abi(d, p.get());
d(std::forward<decltype(args)>(args)...);
} else {
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
</pre>
<p>第一部分获取原始ABI指针,要么通过移动(如果可能)从入站委托中移出,要么从入站委托中复制。引用计数由原始指针拥有。</p>
<p>第二部分将原始ABI指针包装到带有我们自定义删除器的<code>std::<wbr />unique_ptr</code>中。现在unique指针拥有引用计数,自定义删除器将释放它。</p>
<p>问题在于,自定义删除器的要求之一是:如果使用<code>unique_ptr(p)</code>构造函数,则自定义删除器在构造时不得抛出异常。</p>
<blockquote class="q">
<p><b>[unique.ptr.single.ctor]</b></p>
<pre>constexpr explicit unique_ptr(type_identity_t<pointer> p) noexcept;
</pre>
<p>约束条件:<code>is_<wbr />pointer_<wbr />v<deleter_<wbr />type></code>为<code>false</code>,并且<code>is_<wbr />default_<wbr />constructible_<wbr />v<deleter_<wbr />type></code>为<code>true</code>。</p>
<p>前置条件:<code>D</code>满足Cpp17DefaultConstructible要求,并且<span style="border: solid 1px currentcolor;">构造不会抛出异常</span>。</p>
</blockquote>
<p>但我们的自定义删除器可能会在<code>CoGetObjectContext</code>失败时抛出异常。因此它不满足前置条件。</p>
<p>我们可以通过使用接受显式删除器的构造函数来修复,该删除器可以移动构造存储的删除器。如果发生异常,则发生在参数创建期间,而不是在<code>unique_<wbr />ptr</code>构造函数内部。</p>
<pre> if (d.try_as<::INoMarshal>()) {
void* p;
if constexpr (std::is_reference_v<Delegate>) {
p = winrt::detach_abi(d);
} else {
winrt::copy_to_abi(d, p);
}
return
[p = std::unique_ptr<void, in_context_deleter>(p, <span style="border: solid 1px currentcolor;">{}</span>),
token = get_context_token()](auto&&...args) {
if (token == get_context_token()) {
std::remove_reference_t<Delegate> d;
winrt::copy_from_abi(d, p.get());
d(std::forward<decltype(args)>(args)...);
} else {
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
</pre>
<p>好了,现在我们应该完成了吧?</p>
<p>不,仍然有问题。</p>
<p>下次继续。</p>
<p>本文<a href="https://devblogs.microsoft.com/oldnewthing/20260727-00/?p=112566">在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分</a>首次发表在<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>上。</p>
查看缓存全文
缓存时间: 2026/07/28 06:24
# 在 C++/WinRT 中制作敏捷版本的 Windows 运行时委托,第 6 部分 - 《旧日新事》
来源:https://devblogs.microsoft.com/oldnewthing/20260727-00?p=112566
之前我们修复了在正确线程上释放不可封送委托的问题(https://devblogs.microsoft.com/oldnewthing/20260724-00/?p=112562),看起来已经大功告成了。但我们又遗漏了什么。又一次。
```cpp
if (d.try_as<::INoMarshal>())
{
void* p;
if constexpr (std::is_reference_v<T>)
{
p = winrt::detach_abi(d);
}
else
{
winrt::copy_to_abi(d, p);
}
return [p = std::unique_ptr<...>(p),
token = get_context_token()](auto&&...args)
{
if (token == get_context_token())
{
std::remove_reference_t<T> d;
winrt::copy_from_abi(d, p.get());
d(std::forward<...>(args)...);
}
else
{
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
```
第一部分获取一个原始 ABI 指针:如果可以的话,将其移出入站委托,否则从其复制出来。引用计数由原始指针拥有。
第二部分将原始 ABI 指针包装到带有我们自定义删除器的 `std::unique_ptr` 中。该 unique 指针现在拥有引用计数,自定义删除器将释放它。
问题在于,自定义删除器的一个要求是:如果你使用 `unique_ptr(p)` 构造函数,则自定义删除器在构造时不得抛出异常。
> **[unique.ptr.single.ctor]**
> `constexpr explicit unique_ptr(type_identity_t<T> p) noexcept;`
> 约束:`is_pointer_v<D>` 为 `false` 且 `is_default_constructible_v<D>` 为 `true`。
> 前置条件:`D` 满足 Cpp17DefaultConstructible 要求,且其构造不会抛出异常。
但我们的自定义删除器在 `CoGetObjectContext` 失败时可能会抛出异常,因此它不满足前置条件。我们可以通过使用接受显式删除器的构造函数来解决,该删除器可以移动构造到存储的删除器中。如果发生异常,它发生在参数创建期间,而不是在 `unique_ptr` 构造函数内部。
```cpp
if (d.try_as<::INoMarshal>())
{
void* p;
if constexpr (std::is_reference_v<T>)
{
p = winrt::detach_abi(d);
}
else
{
winrt::copy_to_abi(d, p);
}
return [p = std::unique_ptr<...>(p, {}),
token = get_context_token()](auto&&...args)
{
if (token == get_context_token())
{
std::remove_reference_t<T> d;
winrt::copy_from_abi(d, p.get());
d(std::forward<...>(args)...);
}
else
{
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
```
好了,现在总该完事了吧?不,还是有错。下次继续。
### 分类
### 主题
## 作者
**Raymond Chen**
Raymond 参与 Windows 演变已有 30 多年。2003 年,他创办了名为“The Old New Thing”的网站,其流行程度远超他最疯狂的想象——这一发展至今仍让他感到毛骨悚然。该网站催生了一本书,巧合的是书名也叫《The Old New Thing》(Addison Wesley, 2007)。他偶尔会出现在 Windows Dev Docs Twitter 账号上,讲述一些毫无实用信息的故事。
相似文章
在 C++/WinRT 中制作 Windows Runtime 委托的敏捷版本,第5部分
本文继续系列文章,介绍如何在 C++/WinRT 中实现敏捷委托,解决在正确上下文中销毁非敏捷委托的问题,方法是使用自定义删除器和 IContextCallback。
在C++/WinRT中创建Windows Runtime委托的敏捷版本,第8部分
本文讨论了在C++/WinRT中创建敏捷委托时修复异常安全性问题,解决了未指定lambda捕获构造顺序导致的引用泄漏。
在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,第 9 部分
Raymond Chen 继续他的系列文章,讨论在 C++/WinRT 中创建敏捷版 Windows Runtime 委托,并比较 C++/WinRT、C++/CX 和 WRL 如何处理不可封送委托和敏捷引用创建。
在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,第一部分
本文介绍如何通过将委托包装在 agile_ref 中来在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,并承诺在第二部分中提供更多细节。
在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分
这篇博客文章讨论了在C++/WinRT中处理实现INoMarshal接口的Windows运行时代理,提供了一个敏捷的代理包装器,通过检查调用上下文来避免封送错误。