{"id":105617,"date":"2021-08-30T07:00:00","date_gmt":"2021-08-30T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105617"},"modified":"2023-09-14T09:56:38","modified_gmt":"2023-09-14T16:56:38","slug":"20210830-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210830-00\/?p=105617","title":{"rendered":"The various ways of moving between C++\/WinRT and classic COM"},"content":{"rendered":"<p>Windows Runtime language projections create parallel universes with types that correspond to the ABI types, but which are expressed in a projection-specific way. For the C++ projections (C++\/WinRT and C++\/CX), these projected types go even further: Not only do they correspond to the ABI types, but their internal layouts are identical to the ABI types. This means that you can use reinrepret-casting to convert between them.<\/p>\n<p>Suppose we have a Windows Runtime <code>Contoso.<wbr \/>Widget<\/code> with its associated default interface <code>Contoso.<wbr \/>IWidget<\/code>.<\/p>\n<p>C++\/WinRT and C++\/CX set up parallel universes like this:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; font-size: 90%;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th style=\"border: solid 1px currentcolor;\">C++\/WinRT<\/th>\n<th style=\"border: solid 1px currentcolor;\">ABI<\/th>\n<th style=\"border: solid 1px currentcolor;\">C++\/CX<\/th>\n<\/tr>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<\/tbody>\n<tbody style=\"border: double 3px currentcolor;\">\n<tr>\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Contoso::Widget<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>ABI::Contoso::Widget*<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Contoso::Widget^<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Contoso::IWidget<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>ABI::Contoso::IWidget*<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Contoso::IWidget^<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<tbody>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<tr style=\"border: double 3px currentcolor;\">\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Windows::Foundation::IInspectable<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>::IInspectable*<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Platform::Object^<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<tr style=\"border: double 3px currentcolor;\">\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Windows::Foundation::IUnknown<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>::IUnknown*<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<tr style=\"border: double 3px currentcolor;\">\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::hstring<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>::HSTRING<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Platform::String^<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<tr style=\"border: double 3px currentcolor;\">\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Contoso::SomeStruct<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>ABI::Contoso::SomeStruct<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Contoso::SomeStruct<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"height: .25ex;\" colspan=\"3\">\u00a0<\/td>\n<\/tr>\n<tr style=\"border: double 3px currentcolor;\">\n<td style=\"border: solid 1px currentcolor;\"><tt>winrt::Contoso::SomeEnum<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>ABI::Contoso::SomeEnum<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Contoso::SomeEnum<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>All of the entities in a block have the same internal representation and are therefore interchangeable at the ABI. They are just wrapped in different types.<\/p>\n<p>It&#8217;s not too difficult to move up and down the columns, since you are staying inside the same family of types. But moving between columns is usually much uglier, since you&#8217;re travelling between universes.<\/p>\n<p>A case where you may find yourself needing to move between columns is when you want to use a classic COM interface exposed by a Windows Runtime object. Classic COM interfaces exist only in the ABI world; there is no corresponding version in C++\/WinRT or C++\/CX.<\/p>\n<p>One way to move between the C++\/WinRT and ABI columns is to use C++\/WinRT ABI-interop functions.<\/p>\n<p>For getting ABI pointers out of C++\/WinRT reference types and strings:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Expression<\/th>\n<th><code>x<\/code><\/th>\n<th><code>v<\/code> receives<\/th>\n<th>Notes<\/th>\n<\/tr>\n<tr>\n<td><code>v = get_abi(x)<\/code><\/td>\n<td>Unchanged<\/td>\n<td>Non-refcounted pointer<\/td>\n<td>Lifetime controlled by <code>x<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>copy_to_abi(x, v)<\/code><\/td>\n<td>Unchanged<\/td>\n<td>Refcounted pointer<\/td>\n<td>New refcount added<\/td>\n<\/tr>\n<tr>\n<td><code>v = detach_abi(x)<\/code><\/td>\n<td>Emptied<\/td>\n<td>Refcounted pointer<\/td>\n<td>Ownership transfer<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>And for putting ABI pointers into C++\/WinRT reference types and strings:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Expression<\/th>\n<th><code>x<\/code>&#8216;s old value<\/th>\n<th><code>v<\/code><\/th>\n<th>Notes<\/th>\n<\/tr>\n<tr>\n<td><code>*put_abi(x) = v<\/code><\/td>\n<td>Released<\/td>\n<td>No longer owning<\/td>\n<td>Ownership transfer<\/td>\n<\/tr>\n<tr>\n<td><code>copy_from_abi(x, v)<\/code><\/td>\n<td>Released<\/td>\n<td>Still owning<\/td>\n<td>Reference-counted copy<\/td>\n<\/tr>\n<tr>\n<td><code>attach_abi(x, v)<\/code><\/td>\n<td>Released<\/td>\n<td>No longer owning<\/td>\n<td>Ownership transfer<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A customer had a C++\/WinRT <code>Contoso.<wbr \/>Widget<\/code> and wanted to get the raw ABI <code>::IUnknown*<\/code> from it, so that they could pass it to <code>Co\u00adSet\u00adProxy\u00adBlanket<\/code>. Here&#8217;s one way to do it:<\/p>\n<pre>winrt::check_hresult(\r\n    CoSetProxyBlanket(\r\n        <span style=\"border: solid 1px currentcolor;\">reinterpret_cast&lt;::IUnknown*&gt;(winrt::get_abi(thing))<\/span>,\r\n        RPC_C_AUTHN_DEFAULT,\r\n        RPC_C_AUTHZ_DEFAULT,\r\n        COLE_DEFAULT_PRINCIPAL,\r\n        RPC_C_AUTHN_LEVEL_DEFAULT,\r\n        RPC_C_IMP_LEVEL_IMPERSONATE,\r\n        nullptr \/*pAuthInfo*\/,\r\n        EOAC_NONE));\r\n<\/pre>\n<p>Another way to do it is to use <code>winrt::com_ptr<\/code>, which gives you a little bridge to the classic COM world, provided you include <code>&lt;unknwn.h&gt;<\/code> before including any C++\/WinRT header files.<\/p>\n<pre>winrt::check_hresult(\r\n    CoSetProxyBlanket(\r\n        <span style=\"border: solid 1px currentcolor;\">thing.as&lt;::IUnknown&gt;().get()<\/span>,\r\n        RPC_C_AUTHN_DEFAULT,\r\n        RPC_C_AUTHZ_DEFAULT,\r\n        COLE_DEFAULT_PRINCIPAL,\r\n        RPC_C_AUTHN_LEVEL_DEFAULT,\r\n        RPC_C_IMP_LEVEL_IMPERSONATE,\r\n        nullptr \/*pAuthInfo*\/,\r\n        EOAC_NONE));\r\n<\/pre>\n<p>If you&#8217;re the sort of person who worries about such things, do note that <code>as()<\/code> performs a <code>Query\u00adInterface<\/code> call, whereas <code>get_abi<\/code> is basically free because it just reaches in and gives you the embedded pointer.<\/p>\n<p>Structures and enumerations are simpler to manage, since there is no reference count. You can just <code>reinterpret_cast<\/code> between the types:<\/p>\n<pre>winrt::Contoso::SomeStruct s1;\r\nABI::Contoso::SomeStruct s2 = reinterpret_cast&lt;ABI::Contoso::SomeStruct&amp;&gt;(s1);\r\n\r\nwinrt::Contoso::SomeEnum e1;\r\nABI::Contoso::SomeEnum e2 = reinterpret_cast&lt;ABI::Contoso::SomeEnum&gt;(e1);\r\n<\/pre>\n<p><b>Bonus chatter<\/b>: Really, you can just <code>reinterpret_cast&lt;T&amp;&gt;<\/code> to move between columns, even for reference types, since the internal representations are the same.<\/p>\n<pre>winrt::Anything a1;\r\nABI::Anything a2;\r\n::Anything a3;\r\n\r\na1 = reinterpret_cast&lt;winrt::Anything&amp;&gt;(a2);\r\na1 = reinterpret_cast&lt;winrt::Anything&amp;&gt;(a3);\r\n\r\na2 = reinterpret_cast&lt;ABI::Anything&amp;&gt;(a1);\r\na2 = reinterpret_cast&lt;ABI::Anything&amp;&gt;(a3);\r\n\r\na3 = reinterpret_cast&lt;::Anything&amp;&gt;(a1);\r\na3 = reinterpret_cast&lt;::Anything&amp;&gt;(a2);\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Crossing to the other side.<\/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-105617","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Crossing to the other side.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105617","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=105617"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105617\/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=105617"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105617"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105617"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}