构建仅在宿主套间内起效的伪敏捷包装器(第四部分)
文章背景与核心概要
本系列文章探讨了 Windows COM 编程中套间(Apartment)与敏捷对象(Agile Object)的技术细节。在第四部分中,作者对上一篇中建立的“伪敏捷(Fake Agile)”全局接口表(GIT)包装器进行了性能与资源管理层面的优化。核心改进是将不可封送的 COM 对象从深拷贝模式改为通过右值引用(rvalue references)进行高效“移动(Move)”;同时深入阐述了 C++ 类模板参数推导(CTAD)与转发引用的交互机制,并为下一篇引入 std::unique_ptr 简化资源释放埋下了伏笔。
概要
Summary
在系列文章的第四部分中,我们通过优化资源所有权对“伪敏捷” COM 包装器进行了微调。我们更新了 force_marshal 包装器及构造函数的底层管线,不再复制不可封送的 COM 对象,而是通过右值引用高效地对其进行移动。我们还探讨了类模板参数推导(CTAD)如何与转发引用(forwarding references)相互作用,并为下一篇文章中使用 std::unique_ptr 实现更简洁的资源管理方案奠定了基础。
In this fourth installment of the series, we fine-tune our "fake agile" COM wrapper by optimizing resource ownership. Instead of copying non-marshalable COM objects, we update the
force_marshalwrapper and constructor plumbing to move them efficiently via rvalue references. We also explore how Class Template Argument Deduction (CTAD) interacts with forwarding references and set the stage for a cleaner resource management approach usingstd::unique_ptrin the next post.
采用移动而非复制
Moving Rather Than Copying
上次,我们成功确立了利用全局接口表(GIT)来持有伪敏捷包装器的方案——该包装器在技术上是敏捷的,尽管它在其宿主套间(home apartment)之外并没有实际用途。正如之前指出的那样,这个实现方案还有几个可以微调优化的空间。
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.
其中一个关键改进是移动(move)不可封送的 COM 对象,而不是对其进行复制(copy)。这需要将引用一路完美转发(forward)到 force_marshal 包装器内部:
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.
贯通管道引用
Plumbing the Reference
接下来,我们将这个引用一路向下贯通到 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)...);
};
}
关于推导指引(Deduction Guides)的说明
A Note on Deduction Guides
请注意,我们并不需要更新 fake_agile_ref 的推导指引来添加转发支持。
Notice that we didn't have to update the deduction guides for
fake_agile_refto add forwarding support.
推导指引仅用于在构造函数调用时进行匹配,以确定应当使用哪种模板特化(specialization),但它们并不用于真正地调用构造函数——真正的调用是通过针对构造函数本身的重载匹配来完成的。因此:
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:
- 如果有人尝试根据右值引用创建
fake_agile_ref,针对const&的推导指引会将类模板参数推导(CTAD)引导至正确的特化版本。 - 当编译器随后寻找实际的构造函数时,它能够成功找到接收右值引用的那个构造函数。
- 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.- When the compiler actually looks for a constructor, it successfully finds the one that takes an rvalue reference.
下期预告
A Teaser for Next Time
还记得我之前吐槽过我们无法使用 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.
原作者:Raymond Chen,发表于 The Old New Thing
Original post by Raymond Chen, published on The Old New Thing.