在 C++/WinRT 中制作 Windows Runtime 委托的敏捷版本,第5部分
摘要
本文继续系列文章,介绍如何在 C++/WinRT 中实现敏捷委托,解决在正确上下文中销毁非敏捷委托的问题,方法是使用自定义删除器和 IContextCallback。
<p>到目前为止,我们已经通过将不可封送委托包装在一个委托中来处理<a title="在 C++/WinRT 中制作 Windows Runtime 委托的敏捷版本,第4部分" href="https://devblogs.microsoft.com/oldnewthing/20260723-00/?p=112560">不可封送委托的情况</a>,如果以需要封送的方式使用该委托,则会失败并返回 <code>CO_<wbr />E_<wbr />NOT_<wbr />SUPPORTED</code>。</p>
<p>但我们遗漏了一些东西。</p>
<p>让我们再看一次。</p>
<pre> if (d.try_as<::INoMarshal>()) {
return [d, token = get_context_token(),
context = winrt::capture<IContextCallback>(CoGetObjectContext)](auto&&...args) {
if (token == get_context_token()) {
d(std::forward<decltype(args)>(args)...);
} else {
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
</pre>
<p>虽然我们只从原始上下文复制并调用委托,但我们仍然可能在错误的上下文中<i>销毁</i>它。</p>
<p>这是一个严重的问题,不仅因为非敏捷委托可能未使用线程安全的原子指令来管理其引用计数,而且更糟糕的是,如果释放操作将引用计数降至零,委托将在错误的线程上销毁,这可能会造成很多问题。</p>
<p>我们必须在其原始上下文中销毁非敏捷委托。我们可以通过 <code>std::<wbr />unique_<wbr />ptr</code> 和自定义删除器来实现。 <code>std::<wbr />unique_<wbr />ptr</code> 处理所有移动操作,删除器清理最后一个指针。</p>
<pre>struct in_context_deleter
{
winrt::com_ptr<IContextCallback> context =
winrt::capture<IContextCallback>(CoGetObjectContext);
void operator()(void* p)
{
if (p) {
ComCallData data{};
data.pUserDefined = p;
context->ContextCallback([](ComCallData* data) {
winrt::IUnknown{ data->pUserDefined, winrt::take_ownership_from_abi };
return S_OK;
}, &data, __uuidof(IContextCallback), 5, nullptr);
}
}
};
</pre>
<p>这种有状态的删除器记住了用于最终销毁的上下文。当需要销毁时,我们通过 <code>IContextCallback::<wbr />ContextCallback</code> 切换到目标上下文,并将原始指针的所有权转移到 <code>winrt::<wbr />IUnknown</code> 中。 <code>winrt::<wbr />IUnknown</code> 的析构函数将执行释放操作。</p>
<p>我们可以将此有状态删除器用于原始委托指针周围。</p>
<pre>// 暂时不要使用此代码 - 请阅读到系列文章结尾
template<typename Delegate>
std::remove_reference_t<Delegate> make_agile_delegate(Delegate&& d)
{
if (d.try_as<::IAgileObject>()) {
return d;
}
if (d.try_as<::INoMarshal>()) {
<span style="border: solid 1px currentcolor; border-bottom: none;">void* p; </span>
<span style="border: 1px currentcolor; border-style: none solid;">if constexpr (std::is_reference_v<Delegate>) {</span>
<span style="border: 1px currentcolor; border-style: none solid;"> p = winrt::detach_abi(d); </span>
<span style="border: 1px currentcolor; border-style: none solid;">} else { </span>
<span style="border: 1px currentcolor; border-style: none solid;"> winrt::copy_to_abi(d, p); </span>
<span style="border: solid 1px currentcolor; border-top: none;">} </span>
return
[<span style="border: solid 1px currentcolor;">p = std::unique_ptr<void, in_context_deleter>(p),</span>
<span style="border: dashed 1px currentcolor;">/* <span style="text-decoration: line-through;">context = winrt::capture<IContextCallback>(CoGetObjectContext),</span> */</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);
}
};
} else {
return [agile = winrt::agile_ref(d)](auto&&...args) {
return agile.get()(std::forward<decltype(args)>(args)...);
};
}
}
</pre>
<p>第一个块获取一个原始 ABI 指针,如果可能的话通过将其移出入站委托,否则通过从入站委托复制。</p>
<p>第二部分将原始 ABI 指针包装在带有我们自定义删除器的 <code>std::<wbr />unique_ptr</code> 中,并且自定义删除器确保原始委托的 <code>Release</code> 在正确的上下文中发生。</p>
<p>我们完成了吗?</p>
<p>没有!</p>
<p>更多内容下次再说。</p>
<p>这篇文章 <a href="https://devblogs.microsoft.com/oldnewthing/20260724-00/?p=112562">在 C++/WinRT 中制作 Windows Runtime 委托的敏捷版本,第5部分</a> 首次出现在 <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a> 上。</p>
查看缓存全文
缓存时间: 2026/07/24 17:02
# 使用 C++/WinRT 制作敏捷版 Windows 运行时委托,第 5 部分 - 旧事重提
来源:https://devblogs.microsoft.com/oldnewthing/20260724-00?p=112562
到目前为止,我们已经处理了不可封送委托(https://devblogs.microsoft.com/oldnewthing/20260723-00/?p=112560)的情况——将其包装在一个委托中,如果以需要封送的方式使用,则返回 `CO_E_NOT_SUPPORTED`。但我们遗漏了一些东西。让我们再看一遍。
```cpp
if (d.try_as<::INoMarshal>())
{
return [d, token = get_context_token(), context = winrt::capture(CoGetObjectContext)](auto&&...args)
{
if (token == get_context_token())
{
d(std::forward(args)...);
}
else
{
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
```
虽然我们仅在原始上下文中复制并调用委托,但我们仍然在**可能错误的上下文**中**析构**它。这是一个严重的问题,不仅因为非敏捷委托可能没有使用线程安全的原子指令来管理其引用计数,更糟糕的是,如果释放操作将引用计数降至零,委托将在错误的线程上析构,这可能会引发大量问题。我们必须在其原始上下文中析构非敏捷委托。
我们可以通过 `std::unique_ptr` 和自定义删除器来实现这一点。`std::unique_ptr` 处理所有移动操作,而删除器则清理最后一个指针。
```cpp
struct in_context_deleter
{
winrt::com_ptr context = winrt::capture(CoGetObjectContext);
void operator()(void* p)
{
if (p)
{
ComCallData data{};
data.pUserDefined = p;
context->ContextCallback([](ComCallData* data) {
winrt::IUnknown{ data->pUserDefined, winrt::take_ownership_from_abi };
return S_OK;
}, &data, __uuidof(IContextCallback), 5, nullptr);
}
}
};
```
这个有状态的删除器记住了用于最终析构的上下文。当需要析构时,我们通过 `IContextCallback::ContextCallback` 切换到目标上下文,并将原始指针的所有权转移到 `winrt::IUnknown`。`winrt::IUnknown` 的析构函数将执行释放操作。
我们可以将这个有状态的删除器用于原始委托指针。
```cpp
// 请先不要使用 - 请阅读到本系列文章结尾
template<typename Delegate>
std::remove_reference_t<Delegate> make_agile_delegate(Delegate&& d)
{
if (d.try_as<::IAgileObject>())
{
return d;
}
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),
/* context = winrt::capture(CoGetObjectContext), */
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(args)...);
}
else
{
throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
}
};
}
else
{
return [agile = winrt::agile_ref<Delegate>(d)](auto&&...args)
{
return agile.get()(std::forward(args)...);
};
}
}
```
第一个块获取一个原始 ABI 指针,如果可以,则从入站委托中移出;否则从入站委托中复制。第二部分将原始 ABI 指针包装在带有自定义删除器的 `std::unique_ptr` 中,该删除器确保原始委托的 `Release` 发生在正确的上下文中。
我们完成了吗?没有!下次继续。
### 分类
### 主题
## 作者
Raymond Chen
Raymond 参与 Windows 的演变已有 30 多年。2003 年,他创办了一个名为“旧事重提”的网站,其受欢迎程度远远超出了他最疯狂的想象,这一发展仍让他感到不自在。该网站还衍生出了一本书,巧合的是,书名也是《旧事重提》(Addison Wesley,2007 年)。他偶尔会出现在 Windows Dev Docs Twitter 账户上,讲述一些毫无有用信息的故事。
相似文章
在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,第一部分
本文介绍如何通过将委托包装在 agile_ref 中来在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,并承诺在第二部分中提供更多细节。
在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分
这篇博客文章讨论了在C++/WinRT中处理实现INoMarshal接口的Windows运行时代理,提供了一个敏捷的代理包装器,通过检查调用上下文来避免封送错误。
用C++/WinRT制作敏捷版Windows运行时委托,第4部分
本文讨论在C++/WinRT中实现敏捷的Windows运行时委托,重点介绍使用CoGetContextToken比较上下文令牌以提高效率,而不是比较COM对象。
如果C#和JavaScript允许我多次等待Windows Runtime异步操作,为什么C++/WinRT不行?
Raymond Chen解释了为什么C++/WinRT不像C#、JavaScript和Python那样允许多次等待异步操作,其原因是没有标准库的task类型,以及不为你未使用的功能付费的原则。
在多个协程之间共享单个Windows Runtime IAsyncOperation的结果,第3部分
本文讨论了一个C++/WinRT模式,用于缓存Windows Runtime IAsyncOperation的结果,包括处理失败的情况,以便多个协程可以共享缓存的结果或异常。