{"id":112573,"date":"2026-07-30T07:00:00","date_gmt":"2026-07-30T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112573"},"modified":"2026-07-30T21:58:58","modified_gmt":"2026-07-31T04:58:58","slug":"20260730-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260730-00\/?p=112573","title":{"rendered":"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 9"},"content":{"rendered":"<p>Over half of the time we spent trying to make an agile version of a Windows Runtime delegate in C++\/WinRT was dealing with the case of <a title=\"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 8\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260729-00\/?p=112570\"> a delegate that declares non-marshalability<\/a>. But how much does it matter?<\/p>\n<p>I looked at the three major C++ implementations of the Windows Runtime: C++\/WinRT, C++\/CX, and WRL.<\/p>\n<p>The C++\/WinRT implementation <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/blob\/55f1b452aca069d6ac7eaad3e05cc1058fc39d27\/strings\/base_delegate.h#L88\"> has an optimization for <code>IAgile\u00adObject<\/code><\/a>, but for objects that aren&#8217;t agile, <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/blob\/55f1b452aca069d6ac7eaad3e05cc1058fc39d27\/strings\/base_delegate.h#L95\"> it just goes directly to <code>agile_<wbr \/>ref<\/code><\/a> without checking for <code>INoMarshal<\/code>. This means that a delegate that declares non-marshability will always be rejected by C++\/WinRT when used as an event handler.<\/p>\n<p>The C++\/CX implementation <a href=\"https:\/\/github.com\/ojdkbuild\/tools_toolchain_vs2013e\/blob\/a6cea36c2e52a571864986ee2957fbd91d6f4ce8\/VC\/include\/agile.h#L207\"> lazy-creates the agile reference to the original delegate when the wrapper is used from a different apartment<\/a>. If the original delegate is non-marshalable, it means that the <code>CO_<wbr \/>E_<wbr \/>NOT\u00adSUPPORTED<\/code> is produced only when the wrapper is used in a way that requires a marshalable delegate.<\/p>\n<p>The WRL implementation does not have an optimization for <code>IAgile\u00adObject<\/code>, although <a href=\"https:\/\/github.com\/tpn\/winsdk-10\/blob\/9b69fd26ac0c7d0b83d378dba01080e93349c2ed\/Include\/10.0.16299.0\/winrt\/wrl\/event.h#L278\"> it mentions it as a possible optimization<\/a>. It always creates the agile reference eagerly, which means that if the original delegate is non-marshalable, it cannot be added to an agile event source.<\/p>\n<p>Okay, so let&#8217;s summarize in a table.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th rowspan=\"2\">Event source<\/th>\n<th rowspan=\"2\">C++\/WinRT<\/th>\n<th rowspan=\"2\">C++\/CX<\/th>\n<th colspan=\"2\">WRL<\/th>\n<th rowspan=\"2\">Our version<\/th>\n<\/tr>\n<tr>\n<th>single-threaded<\/th>\n<th>multi-threaded<\/th>\n<\/tr>\n<tr>\n<td>Optimize agile delegates<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>N\/A<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Avoid wrapping agile delegates<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>Never wraps<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Agile reference creation<\/td>\n<td>Eager<\/td>\n<td>Lazy<\/td>\n<td>Never<\/td>\n<td>Eager<\/td>\n<td>Eager<\/td>\n<\/tr>\n<tr>\n<td>Non-marshalable delegates<\/td>\n<td>Rejected<\/td>\n<td>Allowed if used<br \/>\nnon-agile-ly<\/td>\n<td>Allowed (always<br \/>\nused non-agile-ly)<\/td>\n<td>Rejected<\/td>\n<td>Allowed if used<br \/>\nnon-agile-ly<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Now, maybe you think we are working too hard. (Maybe we are.) In which case you can remove support for whatever cases you feel you don&#8217;t need.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Maybe it doesn&#8217;t matter.<\/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-112573","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Maybe it doesn&#8217;t matter.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112573","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=112573"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112573\/revisions"}],"predecessor-version":[{"id":112575,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112573\/revisions\/112575"}],"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=112573"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112573"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112573"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}