{"id":112586,"date":"2026-08-04T07:00:00","date_gmt":"2026-08-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112586"},"modified":"2026-08-04T21:04:02","modified_gmt":"2026-08-05T04:04:02","slug":"20260804-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260804-00\/?p=112586","title":{"rendered":"Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 2"},"content":{"rendered":"<p>Last time, <a title=\"Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260803-00\/?p=112582\"> we hatched a plan for holding a reference to an object in another apartment that automatically expires when the apartment runs down<\/a>. Let&#8217;s try to implement that plan.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct fake_agile_ref\r\n{\r\nprivate:\r\n    using Smart = std::conditional_t&lt;\r\n        std::is_base_of_v&lt;winrt::Windows::Foundation::IUnknown, T&gt;,\r\n        T, winrt::com_ptr&lt;T&gt;&gt;;\r\n<\/pre>\n<p>We define <code>Smart<\/code> to represent the smart pointer that holds a <code>T<\/code>. If <code>T<\/code> is a projected type, then it is already a smart pointer. Otherwise, <code>T<\/code> is a COM interface, and we put it inside a <code>com_ptr<\/code>. This is the same pattern that the C++\/WinRT <code>agile_ref&lt;T&gt;<\/code> uses.<\/p>\n<pre>    winrt::com_ptr&lt;IContextCallback&gt; m_context;\r\n    ULONG_PTR m_token = 0;\r\n    winrt::com_ptr&lt;IGlobalInterfaceTable&gt; m_git;\r\n    DWORD m_cookie = 0;\r\n    void* m_raw = nullptr;\r\n<\/pre>\n<p>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.<\/p>\n<p>Next comes a reference to the GIT and a cookie that records the registered reference to the original non-agile COM object.<\/p>\n<p>Finally, we keep a raw (non-refcounted) pointer to the original non-agile COM object.<\/p>\n<p>The fake agile reference is considered &#8220;empty&#8221; 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.<\/p>\n<pre>public:\r\n    fake_agile_ref(std::nullptr_t = nullptr) noexcept {}\r\n<\/pre>\n<p>Constructing an empty <code>fake_<wbr \/>agile_<wbr \/>ref<\/code> is easy: Just leave everything at its initial state. In particular, the <code>m_cookie<\/code> 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.<\/p>\n<pre>    fake_agile_ref(Smart const&amp; p) : m_raw(winrt::get_abi(p))\r\n    {\r\n        if (m_raw) {\r\n            m_context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext);\r\n            m_token = get_context_token();\r\n            m_git = winrt::create_instance&lt;IGlobalInterfaceTable&gt;(CLSID_StdGlobalInterfaceTable);\r\n            winrt::check_hresult(m_git-&gt;RegisterInterfaceInGlobal(\r\n                static_cast&lt;::IUnknown*&gt;(m_raw), __uuidof(IUnknown), &amp;m_cookie));\r\n        }\r\n    }\r\n<\/pre>\n<p>To construct a <code>fake_<wbr \/>agile_<wbr \/>ref<\/code> 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 <code>m_cookie<\/code> 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.<\/p>\n<pre>    fake_agile_ref(fake_agile_ref&amp;&amp; other) noexcept :\r\n        m_context(std::move(other.m_context)),\r\n        m_token(std:exchange(other.m_token, 0)),\r\n        m_git(std::move(other.m_git)),\r\n        m_cookie(std::exchange(other.m_cookie, 0)),\r\n        m_raw(other.m_raw)\r\n    {\r\n    }\r\n<\/pre>\n<p>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&#8217;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&#8217;t know how to copy the cookie.)<\/p>\n<pre>    fake_agile_ref&amp; operator=(fake_agile_ref&amp;&amp; other) noexcept\r\n    {\r\n        using std::swap;\r\n        swap(m_context, other.m_context);\r\n        swap(m_token, other.m_token);\r\n        swap(m_git, other.m_git);\r\n        swap(m_cookie, other.m_cookie);\r\n        swap(m_raw, other.m_raw);\r\n    }\r\n<\/pre>\n<p>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&#8217;t need a copy assignment operator because the declared move assignment operator causes the implicitly-defined copy assignment operator to become deleted.<\/p>\n<pre>    bool empty() const noexcept\r\n    {\r\n        return m_cookie == 0;\r\n    }\r\n\r\n    explicit operator bool() const noexcept\r\n    {\r\n        return !empty();\r\n    }\r\n<\/pre>\n<p>An explicit boolean conversion operator lets callers test the fake agile pointer to see whether it is empty.<\/p>\n<pre>    ~fake_agile_ref()\r\n    {\r\n        if (!empty()) {\r\n            m_git-&gt;RevokeInterfaceFromGlobal(std::exchange(m_cookie, 0));\r\n        }\r\n    }\r\n<\/pre>\n<p>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 <code>unique_ptr<\/code>, but a cookie is not a pointer, and <code>unique_ptr<\/code> works only with pointers.<\/p>\n<pre>    [[nodiscard]] Smart get() const\r\n    {\r\n        if (empty()) {\r\n            return nullptr;\r\n        }\r\n        if (m_token != get_context_token()) {\r\n            throw winrt::hresult_error(CO_E_NOT_SUPPORTED);\r\n        }\r\n\r\n        Smart result{ nullptr };\r\n        winrt::copy_from_abi(result, m_raw);\r\n        return result;\r\n    }\r\n<\/pre>\n<p>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 <code>CO_<wbr \/>E_<wbr \/>NOT_<wbr \/>SUPPORTED<\/code> exception which is the same thing that <code>Ro\u00adGet\u00adAgile\u00adReference<\/code> does.<\/p>\n<p>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.)<\/p>\n<pre>};\r\n<\/pre>\n<p>That ends the definition of <code>fake_<wbr \/>agile_<wbr \/>ref<\/code>, but we&#8217;re not done yet.<\/p>\n<pre>template&lt;typename T&gt; fake_agile_ref(winrt::com_ptr&lt;T&gt; const&amp;)\r\n    -&gt; fake_agile_ref&lt;T&gt;;\r\ntemplate&lt;typename T&gt; fake_agile_ref(T const&amp;)\r\n    -&gt; fake_agile_ref&lt;T&gt;;\r\n<\/pre>\n<p>These deduction guides allow class template argument deduction (CTAD) to deduce the <code>T<\/code> from the constructor parameter: If the constructor parameter is a <code>com_ptr&lt;T&gt;<\/code>, then the template type parameter is <code>T<\/code>. Otherwise, the template type parameter matches the constructor parameter, which we assume is a projected type.<\/p>\n<p>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.<\/p>\n<pre>template&lt;typename Delegate&gt;\r\nstd::remove_reference_t&lt;Delegate&gt; make_agile_delegate(Delegate&amp;&amp; d)\r\n{\r\n    if (d.try_as&lt;::IAgileObject&gt;()) {\r\n        return d;\r\n    }\r\n\r\n    if (d.try_as&lt;::INoMarshal&gt;()) {\r\n        return [agile = <span style=\"border: solid 1px currentcolor;\">fake_agile_ref<\/span>(d)](auto&amp;&amp;...args) {\r\n            return agile.get()(std::forward&lt;decltype(args)&gt;(args)...);\r\n        };\r\n    }\r\n\r\n    return [agile = winrt::agile_ref(d)](auto&amp;&amp;...args) {\r\n        return agile.get()(std::forward&lt;decltype(args)&gt;(args)...);\r\n    };\r\n}\r\n<\/pre>\n<p>Unfortunately, when we take this out for a spin and give it a non-marshalable delegate, it fails at this line:<\/p>\n<pre>            winrt::check_hresult(m_git-&gt;RegisterInterfaceInGlobal(\r\n                static_cast&lt;::IUnknown*&gt;(m_raw), __uuidof(IUnknown), &amp;m_cookie));\r\n<\/pre>\n<p>That&#8217;s because <code>Register\u00adInterface\u00adIn\u00adGlobal<\/code> will not register objects that deny marshalability.<\/p>\n<p>Oh great, so we&#8217;re back to square one.<\/p>\n<p>We&#8217;ll break the cycle of despair next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using a reference stored in the global interface table.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112586","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Using a reference stored in the global interface table.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112586"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112586\/revisions"}],"predecessor-version":[{"id":112587,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112586\/revisions\/112587"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112586"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112586"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}