{"id":107761,"date":"2023-01-30T07:00:00","date_gmt":"2023-01-30T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107761"},"modified":"2023-01-30T07:46:41","modified_gmt":"2023-01-30T15:46:41","slug":"20230130-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230130-00\/?p=107761","title":{"rendered":"Inside C++\/WinRT: Apartment switching: COM without COM"},"content":{"rendered":"<p>One feature of the C++\/WinRT library is that it can operate without the Component Object Model (COM) services. It still uses the COM application binary interface (ABI), but it doesn&#8217;t rely on the system COM services. If they aren&#8217;t available, it will just make do on its own.<\/p>\n<p>This pattern of using COM as an ABI without using any COM services is sometimes called nano-COM. Some other Windows components follow this pattern, such as DirectX and Media Foundation.<\/p>\n<p>In the case where COM is not active, C++\/WinRT acts as if all threads are running in the multi-threaded apartment (MTA), and therefore all apartment-switching operations have no effect.<\/p>\n<p>We have to add fallback code to the places where we ask COM for information or ask it to do something for us.<\/p>\n<pre>inline std::pair&lt;int32_t, int32_t&gt; get_apartment_type() noexcept\r\n{\r\n    int32_t aptType;\r\n    int32_t aptTypeQualifier;\r\n    <span style=\"color: #08f;\">if (0 ==<\/span> WINRT_IMPL_CoGetApartmentType(\r\n        &amp;aptType, &amp;aptTypeQualifier))\r\n    {\r\n        return { aptType, aptTypeQualifier };\r\n    }\r\n    <span style=\"color: #08f;\">else\r\n    {\r\n        return { 1 \/* APTTYPE_MTA *\/,\r\n                 1 \/* APTTYPEQUALIFIER_IMPLICIT_MTA *\/ };\r\n    }<\/span>\r\n}\r\n<\/pre>\n<p>If we can&#8217;t get the apartment type, then we must be running in nano-COM, and we report that we are running in the implicit MTA.<\/p>\n<pre>struct resume_apartment_context\r\n{\r\n    resume_apartment_context() = default;\r\n    resume_apartment_context(std::nullptr_t) :\r\n        m_context(nullptr), <span style=\"color: #08f;\">m_context_type(-1)<\/span> {}\r\n    <span style=\"color: #c65353;\">resume_apartment_context(\r\n        resume_apartment_context const&amp;) = default;\r\n    resume_apartment_context(\r\n    resume_apartment_context&amp;&amp; other) noexcept :\r\n        m_context(std::move(other.m_context)),\r\n        <span style=\"color: #08f;\">m_context_type(std::exchange(\r\n                        other.m_context_type, -1))<\/span> {}\r\n    resume_apartment_context&amp; operator=(\r\n        resume_apartment_context const&amp;) = default;\r\n    resume_apartment_context&amp; operator=(\r\n        resume_apartment_context&amp;&amp; other) noexcept\r\n    {\r\n        m_context = std::move(other.m_context);\r\n        <span style=\"color: #08f;\">m_context_type =\r\n            std::exchange(other.m_context_type, -1);<\/span>\r\n        return *this;\r\n    }<\/span>\r\n\r\n    bool valid() const noexcept\r\n    {\r\n        return <span style=\"color: #08f;\">m_context_type &gt;= 0<\/span>;\r\n    }\r\n\r\n    com_ptr&lt;IContextCallback&gt; m_context =\r\n        <span style=\"color: #08f;\">try_capture<\/span>&lt;IContextCallback&gt;(\r\n            WINRT_IMPL_CoGetObjectContext);\r\n    int32_t m_context_type = get_apartment_type().first;\r\n};\r\n<\/pre>\n<p>If COM is not active, then <code>Co\u00adGet\u00adObject\u00adContext<\/code> fails, and instead of throwing an exception, we just accept the failure, and <code>m_context<\/code> is a null pointer.<\/p>\n<p>Most of the nonsense in the <code>resume_<wbr \/>apartment_<wbr \/>context<\/code> is just making sure that the <code>m_context_<wbr \/>type<\/code> resets to \u22121 when the <code>m_context<\/code> is moved out. It would be a lot simpler if we had access to the <a title=\"Making\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230116-00\/?p=107717\"> <code>movable_primitive<\/code> template type<\/a> we put together some time ago.<\/p>\n<p>We also have to teach the <code>resume_<wbr \/>apartment()<\/code> function to deal with the case where COM is not running.<\/p>\n<pre>inline auto resume_apartment(\r\n    resume_apartment_context const&amp; context,\r\n    coroutine_handle&lt;&gt; handle)\r\n{\r\n    WINRT_ASSERT(context.valid());\r\n    if (<span style=\"color: #08f;\">(context.m_context == nullptr) ||<\/span>\r\n        (context.m_context ==\r\n            <span style=\"color: #08f;\">try_capture<\/span>&lt;IContextCallback&gt;(\r\n                WINRT_IMPL_CoGetObjectContext)))\r\n    {\r\n        handle();\r\n    }\r\n    else if (context.m_context_type == 1 \/* APTTYPE_MTA \/)\r\n    {\r\n        resume_background(handle);\r\n    }\r\n    else if (is_sta_thread())\r\n    {\r\n        resume_apartment_on_threadpool(m_context, handle);\r\n    }\r\n    else\r\n    {\r\n        resume_apartment_sync(m_context, handle);\r\n    }\r\n}\r\n<\/pre>\n<p>If COM is not running, then every thread is the implicit MTA, and we can resume the coroutine anywhere.<\/p>\n<p>Next time, we&#8217;ll look at another hole in our framework: What happens when things go wrong?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Also known as Nano-COM.<\/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-107761","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Also known as Nano-COM.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107761","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=107761"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107761\/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=107761"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107761"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107761"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}