在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分
摘要
这篇博客文章讨论了在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<typename Delegate>
Delegate make_agile_delegate(Delegate const& d)
{
if (d.try_as<::IAgileObject>()) {
return d;
}
<span style="border: solid 1px currentcolor; border-bottom: none;">if (d.try_as<::INoMarshal>()) { </span>
<span style="border: 1px currentcolor; border-style: none solid;"> return [d, context = winrt::capture<IContextCallback>(CoGetObjectContext)](auto&&...args) {</span>
<span style="border: 1px currentcolor; border-style: none solid;"> if (context == winrt::capture<IContextCallback>(CoGetObjectContext)) { </span>
<span style="border: 1px currentcolor; border-style: none solid;"> d(std::forward<decltype(args)>(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&&...args) {
return agile.get()(std::forward<decltype(args)>(args)...);
};
}
</pre>
<p>如果对象具有<code>INoMarshal</code>标记接口,那么我们捕获原始上下文到我们的包装代理中。在调用时,包装器检查调用上下文是否等于捕获的上下文。如果是,则一切正常,我们调用原始代理。否则,我们抛出<code>RoGetAgileReference</code>用于表示“抱歉,我无法封送此对象”的异常。</p>
<p>如果我们对<code>Delegate</code>使用通用引用,当入站代理是右值引用时,我们就能<code>std::move()</code>出去。</p>
<pre>template<typename Delegate>
<span style="border: solid 1px currentcolor;">std::remove_reference_t<Delegate></span> make_agile_delegate(Delegate&& d)
{
if (d.try_as<::IAgileObject>()) {
return d;
}
if (d.try_as<::INoMarshal>()) {
return [<span style="border: solid 1px currentcolor;">d = std::forward<Delegate>(d)</span>,
context = winrt::capture<IContextCallback>(CoGetObjectContext)](auto&&...args) {
if (context == winrt::capture<IContextCallback>(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)...);
};
}
</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 账号上,讲述一些毫无实际价值的故事。
相似文章
在 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 Runtime 委托的敏捷版本,第5部分
本文继续系列文章,介绍如何在 C++/WinRT 中实现敏捷委托,解决在正确上下文中销毁非敏捷委托的问题,方法是使用自定义删除器和 IContextCallback。
用C++/WinRT制作敏捷版Windows运行时委托,第4部分
本文讨论在C++/WinRT中实现敏捷的Windows运行时委托,重点介绍使用CoGetContextToken比较上下文令牌以提高效率,而不是比较COM对象。
在C++/WinRT中创建Windows Runtime委托的敏捷版本,第8部分
本文讨论了在C++/WinRT中创建敏捷委托时修复异常安全性问题,解决了未指定lambda捕获构造顺序导致的引用泄漏。