{"id":106381,"date":"2022-03-24T07:00:00","date_gmt":"2022-03-24T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106381"},"modified":"2022-03-24T07:03:39","modified_gmt":"2022-03-24T14:03:39","slug":"20220324-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220324-00\/?p=106381","title":{"rendered":"Behind C++\/WinRT: How does C++\/WinRT decide which interfaces are implemented?"},"content":{"rendered":"<p>Last time, we <a title=\"Why does C++\/WinRT say that first_interface is not a member of winrt::impl::interface_list&lt;&gt;?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220323-00\/?p=106378\"> diagnosed a problem<\/a> by realizing that the <code>unkwn.h<\/code> header had not been included <i>prior<\/i> to including any C++\/WinRT headers, and that means that C++\/WinRT did not activate its code that supports classic COM interfaces.<\/p>\n<p>This is going to be the first in what will probably be a very sporadic series of looking into the C++\/WinRT implementation, as reverse-engineered by me.\u00b9 I&#8217;m writing it in part so that I&#8217;ll be able to refer back to this write-up the next time I have to debug this code. And in part so that there are more people who understand the insides of C++\/WinRT and can help support it. (This is the selfish reason for many of the articles I write: I&#8217;m writing them in order to reduce my own workload.)<\/p>\n<p>It all hangs on <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/blob\/a903a2c107be5a1c80467110aee48d5db074b633\/strings\/base_implements.h#L47\"> this definition of <code>is_<wbr \/>interface<\/code><\/a>:<\/p>\n<pre>#ifdef WINRT_IMPL_IUNKNOWN_DEFINED\r\n\r\ntemplate &lt;typename T&gt;\r\nstruct is_interface : std::disjunction&lt;\r\n    std::is_base_of&lt;Windows::Foundation::IInspectable, T&gt;,\r\n    std::conjunction&lt;\r\n        std::is_base_of&lt;::IUnknown, T&gt;,\r\n        std::negation&lt;is_implements&lt;T&gt;&gt;&gt;&gt; {};\r\n\r\n#else\r\n\r\ntemplate &lt;typename T&gt;\r\nstruct is_interface :\r\n    std::is_base_of&lt;Windows::Foundation::IInspectable, T&gt; {};\r\n\r\n#endif\r\n<\/pre>\n<p>The <code>WINRT_<wbr \/>IMPL_<wbr \/>IUNKNOWN_<wbr \/>DEFINED<\/code> macro is an internal C++\/WinRT macro that remembers whether <code>unknwn.h<\/code> has been included. If so, then <code>::IUnknown<\/code> is defined, and C++\/WinRT can activate classic COM support. Let&#8217;s translate the C++ type traits template meta-programming into something we&#8217;re more familiar with.<\/p>\n<p>One of the main tools of the C++ type traits system is the <code>std::integral_constant&lt;T, v&gt;<\/code>. This is a type that wraps a constant value <code>v<\/code> of type <code>T<\/code>.<\/p>\n<pre>template&lt;typename T, T v&gt;\r\nstruct integral_constant\r\n{\r\n    static constexpr T value = v;\r\n    ... other stuff not relevant here ...\r\n};\r\n<\/pre>\n<p>For example, <code>std::integral_constant&lt;int, 42&gt;::value<\/code> is an integer constant whose value is 42.<\/p>\n<p>This seems pointless, but it&#8217;s not. Template meta-programming doesn&#8217;t have variables; it operates on types. The <code>std::integral_constant<\/code> lets you treat a type as if it were a variable whose value is the <code>integral_constant::value<\/code>.<\/p>\n<p>C++ comes with a number of pre-made integral constants. Relevant today are <code>std::true_type<\/code> and <code>std::false_type<\/code>, which wrap a Boolean <code>true<\/code> or <code>false<\/code>, respectively. And it also comes with some pre-made template types that manipulate them:<\/p>\n<ul>\n<li><code>std::conjunction<\/code> performs a logical <code>and<\/code> on its arguments.\u00b2<\/li>\n<li><code>std::disjunction<\/code> performs a logical <code>or<\/code> on its arguments.<\/li>\n<li><code>std::negation<\/code> performs a logical <code>not<\/code> on its argument.<\/li>\n<\/ul>\n<p>Okay, now we can start taking apart the first expression.<\/p>\n<pre>template &lt;typename T&gt;\r\nstruct is_interface : std::disjunction&lt;\r\n    std::is_base_of&lt;Windows::Foundation::IInspectable, T&gt;,\r\n    std::conjunction&lt;\r\n        std::is_base_of&lt;::IUnknown, T&gt;,\r\n        std::negation&lt;is_implements&lt;T&gt;&gt;&gt;&gt; {};\r\n<\/pre>\n<p>We mentally convert the <code>std::disjunction<\/code> to <code>||<\/code>, the <code>std::conjunction<\/code> to <code>&amp;&amp;<\/code>, and the <code>std::negation<\/code> to <code>!<\/code>.<\/p>\n<pre>template &lt;typename T&gt;\r\nstruct is_interface is true if\r\n    std::is_base_of&lt;Windows::Foundation::IInspectable, T&gt; ||\r\n    (\r\n        std::is_base_of&lt;::IUnknown, T&gt; &amp;&amp;\r\n        !is_implements&lt;T&gt;);\r\n<\/pre>\n<p>Now we can read out the logic. Something is considered an interface if either<\/p>\n<ul>\n<li>It derives from <code>winrt::<wbr \/>Windows::Foundation::<wbr \/>IInspectable<\/code>, or<\/li>\n<li>It derives from <code>::IUnknown<\/code> and is not an <code>implements<\/code>.<\/li>\n<\/ul>\n<p>The rejection of <code>implements<\/code> prevents <code>is_interface<\/code> from misdetecting <code>implements<\/code> as a itself being COM interface.<\/p>\n<p>Onward to the <code>#else<\/code>: If <code>unknwn.h<\/code> was not included, then we use a simpler definition of <code>is_<wbr \/>interface<\/code> that merely detects derivation from <code>winrt::<wbr \/>Windows::Foundation::<wbr \/>IInspectable<\/code>.<\/p>\n<p>In order to detect classic COM interfaces, C++\/WinRT needs <code>::IUnknown<\/code> to have been defined. Otherwise, it has nothing to test as a base class.<\/p>\n<p>So that&#8217;s the quick diagnosis of yesterday&#8217;s problem wherein C++\/WinRT failed to recognize classic COM interfaces.<\/p>\n<p>Next time, we&#8217;ll dig in deeper to how the <code>is_<wbr \/>interface<\/code> definition is used to pick out the interfaces.<\/p>\n<p><b>Bonus chatter<\/b>: As I noted last time, the requirement that you include <code>unknwn.h<\/code> before including C++\/WinRT is no longer present as of C++\/WinRT version 2.0.210922.5. The trick is to forward-declare the <code>::IUnknown<\/code> type so that you can talk about it without knowing what it is. The <code>std::is_base_class<\/code> template type requires only that the proposed derived class be complete. The base class (<code>::IUnknown<\/code>) doesn&#8217;t have to be complete.<\/p>\n<p><b>Exercise<\/b>: Why is it okay for <code>std::is_base_class<\/code> to accept an incomplete base class? How can it possibly detect whether something derives from a class which has no definition?<\/p>\n<p>\u00b9 A lot of learning comes from reverse-engineering. When doing debugging, you are pretty much forced into it.<\/p>\n<p>\u00b2 Actually, <code>std::conjunction<\/code> and <code>std::disjunction<\/code> behave more like their JavaScript equivalent operators <code>&amp;&amp;<\/code> and <code>||<\/code> because they short-circuit and support &#8220;truthiness&#8221;.<\/p>\n<p><b>Answer to exercise<\/b>: If the base class is incomplete, then nothing can derive from it. You can&#8217;t derive from an incomplete type.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Finding the code that does the interface detection.<\/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-106381","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Finding the code that does the interface detection.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106381","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=106381"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106381\/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=106381"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106381"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106381"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}