创建一个技术上敏捷但在原公寓外无效的伪敏捷包装器(第四部分)
文章背景与核心概要
本文是关于构建“伪敏捷(Fake Agile)”COM 包装器系列文章的第四篇。该系列旨在解决 COM 对象在跨公寓(Apartment)调用时的限制问题,通过全局接口表(GIT)实现一种技术上满足敏捷接口要求,但实际仅在原公寓内有效的包装方案。
本篇核心在于优化资源所有权管理,通过引入右值引用(rvalue references)和完美转发(perfect forwarding),实现了非可封送(non-marshalable)COM 对象的高效移动而非复制。此外,文章还探讨了类模板参数推导(CTAD)与转发引用之间的交互机制,并为下一篇利用 std::unique_ptr 简化资源管理逻辑奠定了基础。
移动而非复制
上一次,我们成功制定了使用全局接口表(GIT)来持有伪敏捷包装器的方案,使其在技术上表现为敏捷,尽管它在原公寓之外并无实际用途。正如之前指出的,该实现仍有几个优化空间。
一个关键的改进是移动(move)非可封送的 COM 对象,而不是复制它。这涉及到将引用一路转发到 force_marshal 包装器中:
Last time, we successfully established our plan to use the Global Interface Table (GIT) to hold a fake agile wrapper that is technically agile, even though it isn't useful outside its home apartment. As noted, there are several opportunities for fine-tuning this implementation.
One key improvement is to move the non-marshalable COM object rather than copying it. This involves forwarding the reference all the way into the
force_marshalwrapper:
template<typename Smart>
struct force_marshal :
winrt::implements<force_marshal<Smart>, IUnknown, winrt::non_agile>
{
template<typename Arg>
force_marshal(Arg&& arg) : m_p(std::forward<Arg>(arg)) {}
Smart m_p;
};
force_marshal<Smart> 结构体现在可以接受任何参数,并将其直接转发到智能指针中。因此,如果传入的参数是一个指向智能指针的右值引用,COM 引用将被移动到 force_marshal<Smart> 对象中,而不是被复制。
The
force_marshal<Smart>struct now accepts anything and forwards it directly into the smart pointer. Consequently, if the inbound parameter is an rvalue reference to a smart pointer, the COM reference is moved into theforce_marshal<Smart>object instead of being copied.
引用传递的管道化
接下来,我们将此引用通过 fake_agile_ref 和 make_agile_delegate 的实现一路传递下去:
Next, we plumb this reference all the way down through the
fake_agile_refandmake_agile_delegateimplementations:
template<typename T>
struct fake_agile_ref
{
⟦ ... ⟧
template<typename Arg>
fake_agile_ref(Arg&& p) : m_raw(winrt::get_abi(p))
{
if (m_raw) {
m_context = winrt::capture<IContextCallback>(CoGetObjectContext);
m_token = get_context_token();
m_git = winrt::create_instance<IGlobalInterfaceTable>(CLSID_StdGlobalInterfaceTable);
winrt::check_hresult(m_git->RegisterInterfaceInGlobal(
winrt::make<force_marshal<Smart>>(std::forward<Arg>(p)).get(),
__uuidof(IUnknown), &m_cookie));
}
}
⟦ ... ⟧
};
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>()) {
return [agile = fake_agile_ref(std::forward<Delegate>(d)](auto&&...args) {
return agile.get()(std::forward<decltype(args)>(args)...);
};
}
return [agile = winrt::agile_ref(d)](auto&&...args) {
return agile.get()(std::forward<decltype(args)>(args)...);
};
}
关于推导指南的说明
请注意,我们无需更新 fake_agile_ref 的推导指南(deduction guides)来添加对转发的支持。
推导指南是针对构造函数调用进行匹配,以确定使用哪个模板特化,但它们并不用于实际调用构造函数——那是通过匹配构造函数本身来完成的。因此:
1. 如果有人尝试从右值引用创建 fake_agile_ref,const& 的推导指南会将类模板参数推导(CTAD)引导至正确的特化版本。
2. 当编译器实际查找构造函数时,它会成功找到那个接受右值引用的构造函数。
Notice that we didn't have to update the deduction guides for
fake_agile_refto add forwarding support.Deduction guides are matched against the constructor invocation to determine which template specialization to use, but they are not used for actually invoking the constructor—that happens by matching against the constructors themselves. Therefore: 1. If someone tries to create a
fake_agile_reffrom an rvalue reference, the deduction guide forconst&steers Class Template Argument Deduction (CTAD) toward the correct specialization. 2. When the compiler actually looks for a constructor, it successfully finds the one that takes an rvalue reference.
下期预告
还记得我曾抱怨过无法使用 std::unique_ptr 来避免 fake_agile_ref 中大量用于处理 Cookie 不可复制特性的样板代码吗?
是的,我可能撒谎了。
我们下期再看。
Remember how I complained that we couldn’t use
std::unique_ptrto avoid a lot of boilerplate infake_agile_refto manage the fact that cookies cannot be copied?Yeah, so maybe I lied.
We’ll look at it next time.
Original post by Raymond Chen, published on The Old New Thing.