August 6th, 2026
0 reactions

Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4

Last time, we successfully our plan to use the global interface table to hold created a fake agile wrapper that is technically agile, even though it isn’t useful outside its home apartment. I noted that there are opportunities for fine-tuning.

One thing we can do is move the non-marshalable COM object rather than copying it. This means forwarding the reference all the way into the force_marshal wrapper.

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;
};

The force_marshal<Smart> now takes anything and forwards it into the smart pointer. This means that if the inbound parameter is an rvalue reference to a smart pointer, the COM reference is moved into the force_marshal<Smart> object rather than copied.

Now it’s a matter of plumbing this reference all the way down.

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)...);
    };
}

Note that we didn’t have to update the deduction guides for fake_agile_ref to 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. So if somebody tries to create a fake_agile_ref from an rvalue reference, the deduction guide for const& steers class template argument deduction (CTAD) toward the correct specialization, and then when the compiler actually looks for a constructor, it finds the one that takes an rvalue reference.

Remember how I complained that we couldn’t use std::unique_ptr to avoid a lot of boilerplate in fake_agile_ref to manage the fact that cookies cannot be copied?

Yeah, so maybe I lied.

We’ll look at it next time.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments