{"id":112552,"date":"2026-07-22T07:00:00","date_gmt":"2026-07-22T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112552"},"modified":"2026-07-22T17:16:32","modified_gmt":"2026-07-23T00:16:32","slug":"20260722-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260722-00\/?p=112552","title":{"rendered":"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 3"},"content":{"rendered":"<p>Last time, we <a title=\"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 2\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260721-00\/?p=112550\"> made a small but significant optimization to making an agile version of a Windows Runtime delegate<\/a>. But there&#8217;s another case we missed.<\/p>\n<p>That case is an object that implements the <code>INo\u00adMarshal<\/code> interface, which means &#8220;Do not marshal this object.&#8221; For these objects, the <code>Ro\u00adGet\u00adAgile\u00adReference<\/code> function fails to create an agile reference and returns <code>CO_<wbr \/>E_<wbr \/>NOT_<wbr \/>SUPPORTED<\/code>. This function is what powers the <code>agile_<wbr \/>ref<\/code> class, so if you ask for an <code>agile_<wbr \/>ref<\/code> to an object that refuses to be marshaled, you get an <code>CO_<wbr \/>E_<wbr \/>NOT_<wbr \/>SUPPORTED<\/code> exception.<\/p>\n<p>The catch is that this error is produced at the creation of the agile reference. If in practice all your uses of the agile reference are from the original context, you never actually needed to marshal the object, but too bad, you get the error anyway.<\/p>\n<p>So let&#8217;s teach our agile delegate wrapper about delegates that deny marshalability: If the wrapper is invoked on the same context that the original delegate belongs to, then everything is fine. But if you try to invoke the wrapper from another context, then you get the <code>CO_<wbr \/>E_<wbr \/>NOT_<wbr \/>SUPPORTED<\/code> exception.<\/p>\n<p>Here&#8217;s our first try. (Foreshadowing: Since I call this a &#8220;first try&#8221;, that suggests we&#8217;re going to have a second try.)<\/p>\n<pre>\/\/ Don't use this yet - read to the end of the series\r\n\r\ntemplate&lt;typename Delegate&gt;\r\nDelegate make_agile_delegate(Delegate const&amp; d)\r\n{\r\n    if (d.try_as&lt;::IAgileObject&gt;()) {\r\n        return d;\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (d.try_as&lt;::INoMarshal&gt;()) {                                                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return [d, context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)](auto&amp;&amp;...args) {<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        if (context == winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)) {                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">            d(std::forward&lt;decltype(args)&gt;(args)...);                                          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        } else {                                                                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">            throw winrt::hresult_error(CO_E_NOT_SUPPORTED);                                    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        }                                                                                      <\/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    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>If the object has the <code>INo\u00adMarshal<\/code> marker interface, then we capture the original context into our wrapper delegate. At invoke time, the wrapper checks whether the invoke context equals the captured context. If so, then all is good, and we call the original delegate. Otherwise, we throw the exception that <code>Ro\u00adGet\u00adAgile\u00adReference<\/code> uses to say &#8220;Sorry, I can&#8217;t marshal this object.&#8221;<\/p>\n<p>If we take a universal reference to the <code>Delegate<\/code>, we gain the ability to <code>std::move()<\/code> out of the inbound delegate if it is an rvalue reference.<\/p>\n<pre>template&lt;typename Delegate&gt;\r\n<span style=\"border: solid 1px currentcolor;\">std::remove_reference_t&lt;Delegate&gt;<\/span> 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 [<span style=\"border: solid 1px currentcolor;\">d = std::forward&lt;Delegate&gt;(d)<\/span>,\r\n                context = winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)](auto&amp;&amp;...args) {\r\n            if (context == winrt::capture&lt;IContextCallback&gt;(CoGetObjectContext)) {\r\n                d(std::forward&lt;decltype(args)&gt;(args)...);\r\n            } else {\r\n                throw winrt::hresult_error(CO_E_NOT_SUPPORTED);\r\n            }\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>Next time, we&#8217;ll look at a small optimization we can make to this implementation to reduce the amount of work needed to check the context at invoke time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The object that actively refuses to be marshaled.<\/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-112552","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The object that actively refuses to be marshaled.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112552","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=112552"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112552\/revisions"}],"predecessor-version":[{"id":112553,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112552\/revisions\/112553"}],"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=112552"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112552"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}