{"id":111546,"date":"2025-09-03T07:00:00","date_gmt":"2025-09-03T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111546"},"modified":"2025-09-03T08:02:09","modified_gmt":"2025-09-03T15:02:09","slug":"20250903-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250903-00\/?p=111546","title":{"rendered":"How can I write a C++\/WinRT <CODE>IAsyncOperation&lt;T&gt;<\/CODE> where <CODE>T<\/CODE> is not a Windows Runtime type?, part 1"},"content":{"rendered":"<p>I often get asked about how one could use C++\/WinRT&#8217;s <code>IAsyncOperation&lt;T&gt;<\/code> where <code>T<\/code> is not a Windows Runtime type.<\/p>\n<p>The <code>T<\/code> in <code>IAsyncOperation&lt;T&gt;<\/code> must be a Windows Runtime type because the algorithm for generating an interface ID for <code>IAsyncOperation&lt;T&gt;<\/code> works only on Windows Runtime types.<\/p>\n<p>But all hope is not lost. You just have to decide which rule you want to break.<\/p>\n<p>The best solution is to break the rule &#8220;I&#8217;m using <code>IAsyncOperation&lt;T&gt;<\/code>.&#8221; Instead, use some other coroutine library that supports arbitrary C++ types. Available options include <a href=\"https:\/\/github.com\/lewissbaker\/cppcoro\"> <code>cppcoro::task&lt;T&gt;<\/code><\/a>, <a href=\"https:\/\/github.com\/microsoft\/wil\/blob\/master\/include\/wil\/coroutine.h\"> <code>wil::task&lt;T&gt;<\/code><\/a> (based on my <a title=\"C++ coroutines: The mental model for coroutine promises\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210329-00\/?p=105015\"> <code>simple_task<\/code><\/a>, but expanded to support COM via <code>wil::<wbr \/>com_task&lt;T&gt;<\/code>), and <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/parallel\/concrt\/reference\/task-class?view=msvc-170\"> <code>concurrency::task&lt;T&gt;<\/code><\/a>.<\/p>\n<p>Another option is not to use the <code>T<\/code> to return the result, but rather pass the output parameter as an explicit input.<\/p>\n<pre>winrt::IAsyncAction DoSomethingAsync(std::shared_ptr&lt;std::optional&lt;Widget&gt;&gt; result)\r\n{\r\n    \u27e6 calculations... \u27e7\r\n\r\n    \/\/ Store the result\r\n    result.get()-&gt;emplace(blah);\r\n\r\n    co_return;\r\n}\r\n<\/pre>\n<p>This is the most general case in which the <code>Widget<\/code> is not inexpensively default-constructible. If the <code>Widget<\/code> is cheap to construct, you can avoid the <code>std::optional<\/code>:<\/p>\n<pre>winrt::IAsyncAction DoSomethingAsync(std::shared_ptr&lt;Widget&gt; result)\r\n{\r\n    \u27e6 calculations... \u27e7\r\n\r\n    \/\/ Store the result\r\n    *result = blah;\r\n\r\n    co_return;\r\n}\r\n<\/pre>\n<p>It&#8217;s important that we use a <code>shared_ptr<\/code> to ensure that the result lifetime extends to the point we store it. You might be tempted to do something like this:<\/p>\n<pre>\/\/ Code in italics is wrong\r\nwinrt::IAsyncAction DoSomethingAsync(<i>Widget&amp; result<\/i>)\r\n{\r\n    \u27e6 calculations... \u27e7\r\n\r\n    \/\/ Store the result\r\n    result = blah;\r\n\r\n    co_return;\r\n}\r\n<\/pre>\n<p>The problem is that you have no guarantee that the caller will keep the <code>result<\/code> value through to the completion of the coroutine. If the caller returns before <code>Do\u00adSomething\u00adAsync<\/code> completes (say because it doesn&#8217;t await the result, or it encounters an exception before it can await the result), then the &#8220;Store the result&#8221; will be writing to an already-destroyed object and will corrupt memory.<\/p>\n<p>Another option is to use <code>IAsyncOperation&lt;T&gt;<\/code> but break the rule that <code>T<\/code> is the thing you want to return. You can instead return an <code>IInspectable<\/code> that is hiding another value inside it.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct ValueAsInspectable :\r\n    winrt::implements&lt;ValueAsInspectable&lt;T&gt;,\r\n    winrt::Windows::Foundation::IInspectable&gt;\r\n{\r\n    T value;\r\n\r\n    template&lt;template...Args&gt;\r\n    ValueAsInspectable(Args&amp;&amp;... args) :\r\n        value{ std::forward&lt;Args&gt;(args)... } {}\r\n};\r\n<\/pre>\n<p>You can then use one of these &#8220;values disguised as an <code>IInspectable<\/code>&#8221; as your operation&#8217;s result.<\/p>\n<pre>winrt::IAsyncOperation&lt;winrt::IInspectable&gt; DoSomethingAsync()\r\n{\r\n    \u27e6 calculations... \u27e7\r\n\r\n    co_return winrt::make&lt;ValueAsInspectable&lt;Widget&gt;&gt;(blah, blah);\r\n}\r\n\r\nwinrt::fire_and_forget Sample()\r\n{\r\n    auto obj = co_await DoSomethingAsync();\r\n    auto&amp; widget = winrt::get_self&lt;ValueAsInspectable&lt;Widget&gt;&gt;(obj)-&gt;value;\r\n    widget.Toggle();\r\n}\r\n<\/pre>\n<p>We make a <code>ValueAsInspectable&lt;Widget&gt;&gt;<\/code> and put a <code>Widget<\/code> inside it, constructed from the parameters <code>blah, blah<\/code>. Upon receiving it, we use the fact that we know that this <code>IInspectable<\/code> is really a <code>Value\u00adAs\u00adInspectable&lt;\u00adWidget&gt;<\/code>, and we use <code>get_self<\/code> to access the backing C++ class and obtain a reference to the <code>value<\/code> hiding inside.<\/p>\n<p>Note that this reference&#8217;s lifetime is controlled by the returned object, so you don&#8217;t want to do this:<\/p>\n<pre>\/\/ Code in italics is wrong\r\n    auto&amp; widget = winrt::get_self&lt;ValueAsInspectable&lt;Widget&gt;&gt;(\r\n            <i>co_await DoSomethingAsync()<\/i>\r\n        )-&gt;value;\r\n<\/pre>\n<p>Because that saves a reference to an object inside an <code>IInspectable<\/code> that is about to be destroyed.<\/p>\n<p>This wrapper technique has the downside of requiring you to trust that the <code>IInspectable<\/code> returned by <code>DoSomething<\/code> really is wrapping a <code>Widget<\/code> in the same process. If it&#8217;s a remote object, or if it&#8217;s wrapping something else, or isn&#8217;t a wrapper at all, then you&#8217;re grabbing a reference to garbage.<\/p>\n<p>We&#8217;ll work on addressing these deficiencies next time, but if you have control over both the producer and consumer of the <code>Value\u00adAs\u00adInspectable<\/code>, then this version may be sufficient.<\/p>\n<p>We can add some helpers to make it a bit more ergonomic.<\/p>\n<pre>template&lt;typename T, typename...Args&gt;\r\nwinrt::Windows::Foundation::IInspectable\r\n    MakeValueAsInspectable(Args&amp;&amp;... args)\r\n{\r\n    return winrt::make&lt;ValueAsInspectable&lt;T&gt;&gt;(\r\n        std::forward&lt;Args&gt;(args)...);\r\n}\r\n\r\ntemplate&lt;typename T&gt;\r\nwinrt::Windows::Foundation::IInspectable\r\n    MakeValueAsInspectable(T&amp;&amp; arg) \r\n{\r\n    return winrt::make&lt;ValueAsInspectable&lt;\r\n        std::remove_reference_t&lt;T&gt;&gt;(\r\n        std::forward&lt;T&gt;(arg));\r\n}\r\n<\/pre>\n<p>The first helper lets you write <code>MakeValueAsInspectable(blah)<\/code>, and the type of the <code>Value\u00adAs\u00adInspectable<\/code> will match the type you passed in (after removing references). This lets you write<\/p>\n<pre>co_return MakeValueAsInspectable(42);\r\n<\/pre>\n<p>instead of<\/p>\n<pre>co_return MakeValueAsInspectable&lt;int&gt;(42);\r\n<\/pre>\n<p>The second overload doesn&#8217;t add any value but at least creates consistency, so you can just use <code>Make\u00adValue\u00adAs\u00adInspectable<\/code> everywhere.<\/p>\n<pre>co_return MakeValueAsInspectable(42);\r\nco_return MakeValueAsInspectable&lt;Widget&gt;(blah, blah);\r\n<\/pre>\n<p>Okay, next time, we&#8217;ll look at those safety improvements.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s not representable in the Windows Runtime, but you can smuggle it.<\/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-111546","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It&#8217;s not representable in the Windows Runtime, but you can smuggle it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111546","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=111546"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111546\/revisions"}],"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=111546"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111546"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111546"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}