在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分

The Old New Thing (Raymond Chen) 工具

摘要

这篇博客文章讨论了在C++/WinRT中处理实现INoMarshal接口的Windows运行时代理,提供了一个敏捷的代理包装器,通过检查调用上下文来避免封送错误。

<p>上次,我们<a title="在C++/WinRT中创建Windows运行时代理的敏捷版本,第2部分" href="https://devblogs.microsoft.com/oldnewthing/20260721-00/?p=112550">对创建Windows运行时代理的敏捷版本做了一个虽小但重要的优化</a>。但我们还遗漏了另一种情况。</p> <p>这种情况是一个实现了<code>INoMarshal</code>接口的对象,这意味着“不要封送此对象”。对于这些对象,<code>RoGetAgileReference</code>函数无法创建敏捷引用,并返回<code>CO_E_NOT_SUPPORTED</code>。这个函数正是<code>agile_ref</code>类的动力来源,因此如果你请求一个<code>agile_ref</code>到一个拒绝被封送的对象,就会得到一个<code>CO_E_NOT_SUPPORTED</code>异常。</p> <p>关键在于这个错误是在创建敏捷引用时产生的。如果实际上你对敏捷引用的所有使用都来自原始上下文,你其实根本不需要封送对象,但不幸的是,你仍然会收到错误。</p> <p>所以,让我们教会我们的敏捷代理包装器如何应对拒绝封送的代理:如果包装器在原始代理所属的同一上下文中被调用,那么一切正常。但如果你尝试从另一个上下文调用包装器,则会得到<code>CO_E_NOT_SUPPORTED</code>异常。</p> <p>这是我们的首次尝试。(预先说明:既然我称之为“首次尝试”,暗示我们还将有第二次尝试。)</p> <pre>// 暂时不要使用——请阅读到系列文章结尾 template&lt;typename Delegate&gt; Delegate make_agile_delegate(Delegate const&amp; d) { if (d.try_as&lt;::IAgileObject&gt;()) { return d; } <span style="border: solid 1px currentcolor; border-bottom: none;">if (d.try_as&lt;::INoMarshal&gt;()) { </span> <span style="border: 1px currentcolor; border-style: none solid;"> return [d, context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)](auto&amp;&amp;...args) {</span> <span style="border: 1px currentcolor; border-style: none solid;"> if (context == winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)) { </span> <span style="border: 1px currentcolor; border-style: none solid;"> d(std::forward&lt;decltype(args)&gt;(args)...); </span> <span style="border: 1px currentcolor; border-style: none solid;"> } else { </span> <span style="border: 1px currentcolor; border-style: none solid;"> throw winrt::hresult_error(CO_E_NOT_SUPPORTED); </span> <span style="border: 1px currentcolor; border-style: none solid;"> } </span> <span style="border: 1px currentcolor; border-style: none solid;"> }; </span> <span style="border: solid 1px currentcolor; border-top: none;">} </span> return [agile = winrt::agile_ref(d)](auto&amp;&amp;...args) { return agile.get()(std::forward&lt;decltype(args)&gt;(args)...); }; } </pre> <p>如果对象具有<code>INoMarshal</code>标记接口,那么我们捕获原始上下文到我们的包装代理中。在调用时,包装器检查调用上下文是否等于捕获的上下文。如果是,则一切正常,我们调用原始代理。否则,我们抛出<code>RoGetAgileReference</code>用于表示“抱歉,我无法封送此对象”的异常。</p> <p>如果我们对<code>Delegate</code>使用通用引用,当入站代理是右值引用时,我们就能<code>std::move()</code>出去。</p> <pre>template&lt;typename Delegate&gt; <span style="border: solid 1px currentcolor;">std::remove_reference_t&lt;Delegate&gt;</span> make_agile_delegate(Delegate&amp;&amp; d) { if (d.try_as&lt;::IAgileObject&gt;()) { return d; } if (d.try_as&lt;::INoMarshal&gt;()) { return [<span style="border: solid 1px currentcolor;">d = std::forward&lt;Delegate&gt;(d)</span>, context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)](auto&amp;&amp;...args) { if (context == winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)) { d(std::forward&lt;decltype(args)&gt;(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } return [agile = winrt::agile_ref(d)](auto&amp;&amp;...args) { return agile.get()(std::forward&lt;decltype(args)&gt;(args)...); }; } </pre> <p>下次,我们将看看一个小优化,我们可以在这个实现中做出,以减少在调用时检查上下文所需的工作量。</p> <p>本文<a href="https://devblogs.microsoft.com/oldnewthing/20260722-00/?p=112552">《在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分》</a>首发于<a href="https://devblogs.microsoft.com/oldnewthing">《旧事新知》</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/07/24 05:15

# 在 C++/WinRT 中创建敏捷版本的 Windows 运行时委托,第3部分 - 旧事新说 来源:https://devblogs.microsoft.com/oldnewthing/20260722-00?p=112552 上次,我们对创建敏捷版本的 Windows 运行时委托([链接](https://devblogs.microsoft.com/oldnewthing/20260721-00/?p=112550))进行了微小但重要的优化。但还有另一种情况我们忽略了。这种情况是指实现了 `INoMarshal` 接口的对象,该接口表示“不要封送此对象”。对于这些对象,`RoGetAgileReference` 函数无法创建敏捷引用,并返回 `CO_E_NOT_SUPPORTED`。该函数正是 `agile_ref` 类的核心,因此如果你尝试为拒绝封送的对象创建 `agile_ref`,就会收到 `CO_E_NOT_SUPPORTED` 异常。关键在于,这个错误是在创建敏捷引用时产生的。如果实际上你对敏捷引用的所有使用都发生在原始上下文中,那么你根本不需要封送该对象,但很遗憾,你仍然会遇到这个错误。 因此,让我们教我们的敏捷委托包装器如何处理拒绝封送的委托:如果包装器在与原始委托所属的同一上下文中被调用,那么一切正常。但如果你尝试从另一个上下文中调用包装器,则会抛出 `CO_E_NOT_SUPPORTED` 异常。 以下是我们的初次尝试。(剧透:既然我称它为“初次尝试”,意味着我们还会进行第二次尝试。) ```cpp // 请勿使用此版本 - 请阅读本系列直到最后 template <typename Delegate> Delegate make_agile_delegate(Delegate const& d) { if (d.try_as<::IAgileObject>()) { return d; } if (d.try_as<::INoMarshal>()) { return [d, context = winrt::capture(CoGetObjectContext)](auto&&...args) { if (context == winrt::capture(CoGetObjectContext)) { d(std::forward<decltype(args)>(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } return [agile = winrt::agile_ref(d)](auto&&...args) { return agile.get()(std::forward<decltype(args)>(args)...); }; } ``` 如果对象具有 `INoMarshal` 标记接口,那么我们将原始上下文捕获到包装器委托中。在调用时,包装器检查当前调用上下文是否等于捕获的上下文。如果是,则一切顺利,我们调用原始委托。否则,我们抛出 `RoGetAgileReference` 用来表示“抱歉,我无法封送此对象”的异常。 如果我们采用通用引用来处理 `Delegate`,那么如果传入的委托是右值引用,我们就能通过 `std::move()` 将其移出。 ```cpp template <typename Delegate> std::remove_reference_t<Delegate> make_agile_delegate(Delegate&& d) { if (d.try_as<::IAgileObject>()) { return std::forward<Delegate>(d); } if (d.try_as<::INoMarshal>()) { return [d = std::forward<Delegate>(d), context = winrt::capture(CoGetObjectContext)](auto&&...args) { if (context == winrt::capture(CoGetObjectContext)) { d(std::forward<decltype(args)>(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } return [agile = winrt::agile_ref(d)](auto&&...args) { return agile.get()(std::forward<decltype(args)>(args)...); }; } ``` 下次,我们将探讨一个可对该实现进行的小优化,以减少检查上下文所需的工作量。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与 Windows 的演进已有 30 多年。2003 年,他创办了一个名为“旧事新说”的网站,其受欢迎程度远超他最狂野的想象,这一发展至今仍让他感到不寒而栗。该网站还衍生出了一本书,巧合的是,书名也叫《旧事新说》(Addison Wesley,2007 年)。他偶尔会出现在 Windows Dev Docs Twitter 账号上,讲述一些毫无实际价值的故事。

相似文章