惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
T
Tailwind CSS Blog
小众软件
小众软件
J
Java Code Geeks
人人都是产品经理
人人都是产品经理
博客园_首页
MyScale Blog
MyScale Blog
博客园 - 聂微东
V
Visual Studio Blog
The Cloudflare Blog
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42

The Old New Thing

std::call_once vs. std::async - The Old New Thing Magic statics vs. std::call_once - The Old New Thing Why do Microsoft job levels start in the high 50's instead of starting at a sane number like 1? - The Old New Thing Why didn't Read­Directory­ChangesW provide a way to correlate the two sides of a rename operation? - The Old New Thing How can I remove the Close button from my window caption? - The Old New Thing Why is the x86 undefined instruction called ud2? Why 2? - The Old New Thing What algorithm did Windows XP use to choose your initial user picture? - The Old New Thing A sample use of the winstart.bat file in Windows 95 - The Old New Thing Why don't we allow stacks to be sparse, instead of forcing them to be contiguous? - The Old New Thing What happens if you change a window class's GCL_CB­WND­EXTRA? - The Old New Thing The case of the progress callback that never got called when progress happened - The Old New Thing The perils of binding to value types in XAML - The Old New Thing Microspeak: Funded / unfunded - The Old New Thing AWE does not require PAE, though PAE makes it much more useful - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 2 - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 1 - The Old New Thing In the product end game, every change carries significant risk, episode 2 - The Old New Thing Why didn't the Windows Entertainment Pack just run the MS-DOS version inside an emulator? - The Old New Thing Comparing the two holograms on the Windows 95 box - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function - The Old New Thing On wrapping a callable in a lambda that just calls it with the same parameters - The Old New Thing Why did the Microsoft Entertainment Pack for Windows have a special sticker announcing that it also had Tetris? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing Forcing an ARM64X executable to run as a specific architecture - The Old New Thing A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs - The Old New Thing The comments that go into code versus those that go into the pull request description - The Old New Thing The little-known winstart.bat batch file - The Old New Thing How can I perform a Copy­File&shy in unbuffered mode? - The Old New Thing
Creating a fake agile wrapper that is technically agile b...
Raymond Chen · 2026-08-04 · via The Old New Thing

Last time, we hatched a plan for holding a reference to an object in another apartment that automatically expires when the apartment runs down. Let’s try to implement that plan.

template<typename T>
struct fake_agile_ref
{
private:
    using Smart = std::conditional_t<
        std::is_base_of_v<winrt::Windows::Foundation::IUnknown, T>,
        T, winrt::com_ptr<T>>;

We define Smart to represent the smart pointer that holds a T. If T is a projected type, then it is already a smart pointer. Otherwise, T is a COM interface, and we put it inside a com_ptr. This is the same pattern that the C++/WinRT agile_ref<T> uses.

    winrt::com_ptr<IContextCallback> m_context;
    ULONG_PTR m_token = 0;
    winrt::com_ptr<IGlobalInterfaceTable> m_git;
    DWORD m_cookie = 0;
    void* m_raw = nullptr;

Our fake agile reference starts with a callback context and a context token. These are used to detect whether we are in the correct apartment when it comes time to access the original non-agile COM object.

Next comes a reference to the GIT and a cookie that records the registered reference to the original non-agile COM object.

Finally, we keep a raw (non-refcounted) pointer to the original non-agile COM object.

The fake agile reference is considered “empty” if the cookie is zero, meaning that it does not refer to any object. In the case of an empty fake agile reference, none of the other members contains anything meaningful.

public:
    fake_agile_ref(std::nullptr_t = nullptr) noexcept {}

Constructing an empty fake_agile_ref is easy: Just leave everything at its initial state. In particular, the m_cookie is zero, meaning that there is nothing inside. The values of all the other members are irrelevant, as long as they can be safely destructed.

    fake_agile_ref(Smart const& 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(
                static_cast<::IUnknown*>(m_raw), __uuidof(IUnknown), &m_cookie));
        }
    }

To construct a fake_agile_ref from a smart pointer, we extract the raw pointer and check whether it is null. If so, then the smart pointer is empty, and we leave the m_cookie at zero. But if it is not null, we initialize the context information (so we can recognize this apartment later), and we register the COM object in the GIT to retain a reference to it for as long as the apartment is valid.

    fake_agile_ref(fake_agile_ref&& other) noexcept :
        m_context(std::move(other.m_context)),
        m_token(std:exchange(other.m_token, 0)),
        m_git(std::move(other.m_git)),
        m_cookie(std::exchange(other.m_cookie, 0)),
        m_raw(other.m_raw)
    {
    }

Since we will have a nontrivial destructor, we need copy and move constructors per the Rule of Five. The move constructor merely steals all the content from the source and leaves the source in the empty state. We don’t need to create a copy constructor because the move constructor causes the implicitly-defined copy constructor to become deleted. (The fake agile reference is not copyable because we don’t know how to copy the cookie.)

    fake_agile_ref& operator=(fake_agile_ref&& other) noexcept
    {
        using std::swap;
        swap(m_context, other.m_context);
        swap(m_token, other.m_token);
        swap(m_git, other.m_git);
        swap(m_cookie, other.m_cookie);
        swap(m_raw, other.m_raw);
    }

The fake agile reference also needs a move assignment operator to satisfy the Rule of Five. It just swaps the contents with the assigned-from object. Again, we don’t need a copy assignment operator because the declared move assignment operator causes the implicitly-defined copy assignment operator to become deleted.

    bool empty() const noexcept
    {
        return m_cookie == 0;
    }

    explicit operator bool() const noexcept
    {
        return !empty();
    }

An explicit boolean conversion operator lets callers test the fake agile pointer to see whether it is empty.

    ~fake_agile_ref()
    {
        if (!empty()) {
            m_git->RevokeInterfaceFromGlobal(std::exchange(m_cookie, 0));
        }
    }

We have reached our nontrivial destructor: If we have a GIT cookie, we revoke it. It would have been nice to let this be a custom deleter of a unique_ptr, but a cookie is not a pointer, and unique_ptr works only with pointers.

    [[nodiscard]] Smart get() const
    {
        if (empty()) {
            return nullptr;
        }
        if (m_token != get_context_token()) {
            throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
        }

        Smart result{ nullptr };
        winrt::copy_from_abi(result, m_raw);
        return result;
    }

Here is where the excitement is. To recover the original COM object, we first check if the fake agile pointer is empty. If so, then there is no COM object to return. If the fake agile pointer is nonempty, but we are in the wrong apartment, then we throw the CO_E_NOT_SUPPORTED exception which is the same thing that RoGetAgileReference does.

Otherwise, we are in the correct context. Our cookie is keeping the original object alive, so we can just recover it from the raw pointer. (We could also redeem the cookie from the GIT, but this is faster.)

};

That ends the definition of fake_agile_ref, but we’re not done yet.

template<typename T> fake_agile_ref(winrt::com_ptr<T> const&)
    -> fake_agile_ref<T>;
template<typename T> fake_agile_ref(T const&)
    -> fake_agile_ref<T>;

These deduction guides allow class template argument deduction (CTAD) to deduce the T from the constructor parameter: If the constructor parameter is a com_ptr<T>, then the template type parameter is T. Otherwise, the template type parameter matches the constructor parameter, which we assume is a projected type.

We can now use this fake agile reference as a drop-in replacement for the normal agile reference in the case that the delegate is not marshalable.

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

Unfortunately, when we take this out for a spin and give it a non-marshalable delegate, it fails at this line:

            winrt::check_hresult(m_git->RegisterInterfaceInGlobal(
                static_cast<::IUnknown*>(m_raw), __uuidof(IUnknown), &m_cookie));

That’s because RegisterInterfaceInGlobal will not register objects that deny marshalability.

Oh great, so we’re back to square one.

We’ll break the cycle of despair next time.

Category

Topics

Author

Raymond Chen

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.