在 C++/WinRT 中制作 Windows Runtime 委托的敏捷版本,第5部分

The Old New Thing (Raymond Chen) 工具

摘要

本文继续系列文章,介绍如何在 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&lt;::INoMarshal&gt;()) { return [d, token = get_context_token(), context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)](auto&amp;&amp;...args) { if (token == get_context_token()) { d(std::forward&lt;decltype(args)&gt;(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&lt;IContextCallback&gt; context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext); void operator()(void* p) { if (p) { ComCallData data{}; data.pUserDefined = p; context-&gt;ContextCallback([](ComCallData* data) { winrt::IUnknown{ data-&gt;pUserDefined, winrt::take_ownership_from_abi }; return S_OK; }, &amp;data, __uuidof(IContextCallback), 5, nullptr); } } }; </pre> <p>这种有状态的删除器记住了用于最终销毁的上下文。当需要销毁时,我们通过 <code>IContext­Callback::<wbr />Context­Callback</code> 切换到目标上下文,并将原始指针的所有权转移到 <code>winrt::<wbr />IUnknown</code> 中。 <code>winrt::<wbr />IUnknown</code> 的析构函数将执行释放操作。</p> <p>我们可以将此有状态删除器用于原始委托指针周围。</p> <pre>// 暂时不要使用此代码 - 请阅读到系列文章结尾 template&lt;typename Delegate&gt; std::remove_reference_t&lt;Delegate&gt; make_agile_delegate(Delegate&amp;&amp; d) { if (d.try_as&lt;::IAgileObject&gt;()) { return d; } if (d.try_as&lt;::INoMarshal&gt;()) { <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&lt;Delegate&gt;) {</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&lt;void, in_context_deleter&gt;(p),</span> <span style="border: dashed 1px currentcolor;">/* <span style="text-decoration: line-through;">context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext),</span> */</span> token = get_context_token()](auto&amp;&amp;...args) { if (token == get_context_token()) { std::remove_reference_t&lt;Delegate&gt; d; winrt::copy_from_abi(d, p.get()); d(std::forward&lt;decltype(args)&gt;(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } else { return [agile = winrt::agile_ref(d)](auto&amp;&amp;...args) { return agile.get()(std::forward&lt;decltype(args)&gt;(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 账户上,讲述一些毫无有用信息的故事。

相似文章