{"id":112597,"date":"2026-08-07T07:00:00","date_gmt":"2026-08-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112597"},"modified":"2026-08-06T22:59:30","modified_gmt":"2026-08-07T05:59:30","slug":"20260807-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260807-00\/?p=112597","title":{"rendered":"Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5"},"content":{"rendered":"<p>Last time, I confessed that <a title=\"Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260806-00\/?p=112595\"> I lied when i said that we can&#8217;t use <code>std::<wbr \/>unique_ptr<\/code> to manage the registration cookie<\/a>.<\/p>\n<p>The trick here is that the registration cookie is of type <code>DWORD<\/code>, which fits in a pointer, so we can smuggle the integer value inside a pointer.<\/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\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">struct git_deleter                                                                           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                                                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    winrt::com_ptr&lt;IGlobalInterfaceTable&gt; m_git;                                             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                                                                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    void operator()(void* p)                                                                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    {                                                                                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        m_git-&gt;RevokeInterfaceFromGlobal(static_cast&lt;DWORD&gt;(reinterpret_cast&lt;uintptr_t&gt;(p)));<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                                                                        <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">};                                                                                           <\/span>\r\n\r\n    winrt::com_ptr&lt;IContextCallback&gt; m_context;\r\n    ULONG_PTR m_token = 0;\r\n    <span style=\"border: solid 1px currentcolor;\">std::unique_ptr&lt;void, git_deleter&gt; m_cookie;<\/span>\r\n    void* m_raw = nullptr;\r\n<\/pre>\n<p>Our custom deleter holds a pointer to the Global Interface Table and uses it to revoke the cookie on destruction. The cookie is an integer smuggled inside a pointer, so we cast the pointer back to an integer by passing through a <code>uintptr_t<\/code> to avoid a compiler warning about casting between an integer and pointer of different sizes.<\/p>\n<p>We are relying on the fact that Windows implementations are required to support round-tripping integers through pointers. Macros like <code>MAKEINTRESOURCE<\/code> rely on it. It&#8217;s also codified in Windows with helper functions like <code>PtrToInt<\/code> and <code>IntToPtr<\/code>, but I&#8217;m writing it out for expository purposes rather than using those helpers.<\/p>\n<p>We can then store the Global Interface Table pointer and the corresponding cookie in the <code>unique_ptr<\/code>:<\/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            <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto&amp; git = m_cookie.get_deleter().m_git;                                          <\/span>\r\n            <span style=\"border: 1px currentcolor; border-style: none solid;\">git = winrt::create_instance&lt;IGlobalInterfaceTable&gt;(CLSID_StdGlobalInterfaceTable);<\/span>\r\n            <span style=\"border: solid 1px currentcolor; border-top: none;\">DWORD cookie;                                                                      <\/span>\r\n            winrt::check_hresult(<span style=\"border: solid 1px currentcolor;\">git<\/span>-&gt;RegisterInterfaceInGlobal(\r\n                winrt::make&lt;force_marshal&lt;Smart&gt;&gt;(p).get(),\r\n                __uuidof(IUnknown), &amp;m_cookie));\r\n            <span style=\"border: solid 1px currentcolor;\">m_cookie.reset(reinterpret_cast&lt;void*&gt;(static_cast&lt;uintptr_t&gt;(cookie)));<\/span>\r\n        }\r\n    }\r\n<\/pre>\n<p>And now that we are letting <code>unique_ptr<\/code> manage the lifetime of the cookie, we don&#8217;t need a custom destructor, which allows us to use the Rule of Zero and simply not have any copy or move constructors or assignment operators.<\/p>\n<pre>    \/\/ <span style=\"text-decoration: line-through;\">fake_agile_ref(fake_agile_ref&amp;&amp; other) noexcept :<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    m_context(std::move(other.m_context)),<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    m_token(std:exchange(other.m_token, 0)),<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    m_git(std::move(other.m_git)),<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    m_cookie(std::exchange(other.m_cookie, 0)),<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    m_raw(other.m_raw)<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n\r\n    \/\/ <span style=\"text-decoration: line-through;\">fake_agile_ref&amp; operator=(fake_agile_ref&amp;&amp; other) noexcept<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    using std::swap;<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    swap(m_context, other.m_context);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    swap(m_token, other.m_token);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    swap(m_git, other.m_git);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    swap(m_cookie, other.m_cookie);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    swap(m_raw, other.m_raw);<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n\r\n    \/\/ <span style=\"text-decoration: line-through;\">~fake_agile_ref()<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">    if (m_cookie) {<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">       m_git-&gt;RevokeInterfaceFromGlobal(std::exchange(m_cookie, 0));<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">   }<\/span>\r\n    \/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n<\/pre>\n<p>Since we are storing the cookie in a <code>unique_ptr<\/code>, we need to adjust the <code>empty<\/code> method:<\/p>\n<pre>    bool empty() const noexcept\r\n    {\r\n        return <span style=\"border: solid 1px currentcolor;\">reinterpret_cast&lt;uintptr_t&gt;(m_cookie.get())<\/span> != 0;\r\n    }\r\n<\/pre>\n<p><b>Bonus chatter<\/b>: The Windows Implementation Library (wil) has a class similar to <code>unique_ptr<\/code> called <code>wil::unique_any<\/code> that lets you apply cleanup to any data type, not just a pointer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cheating with <CODE>std::unique_ptr<\/CODE>.<\/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-112597","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Cheating with <CODE>std::unique_ptr<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112597","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=112597"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112597\/revisions"}],"predecessor-version":[{"id":112599,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112597\/revisions\/112599"}],"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=112597"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112597"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112597"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}