{"id":108930,"date":"2023-10-26T07:00:00","date_gmt":"2023-10-26T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108930"},"modified":"2023-10-26T09:27:29","modified_gmt":"2023-10-26T16:27:29","slug":"20231026-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231026-00\/?p=108930","title":{"rendered":"How to support a COM interface conditionally in C++\/WinRT"},"content":{"rendered":"<p>We saw some time ago that <a title=\"The C++\/WinRT query_interface_tearoff extension point, and using it for COM aggregation\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210913-00\/?p=105680\"> the C++\/WinRT library provides an extension point for adding additional interfaces that aren&#8217;t declared in your <code>implements<\/code> template<\/a>. But what about the reverse case? What if you want to <i>remove<\/i> an interface that was listed in your <code>implements<\/code> template?<\/p>\n<p>You might want to do this if you want to support an interface only conditionally. Just be careful to follow the rules: Every attempt to query for a specific interface from an object must return a consistent result (either always succeed for that interface or always fail for that interface).<\/p>\n<p>The extension point for removing an interface in C++\/WinRT is the <code>find_interface<\/code> virtual method: We can override it to filter out interfaces we don&#8217;t like.<\/p>\n<pre>struct Widget : winrt::implements&lt;\r\n    Widget, winrt::IWidget, winrt::IStringable&gt;\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">void* find_interface(winrt::guid const&amp; id)          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    const noexcept override                          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ If \"Stringable\" is not enabled,               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ then don't support IStringable.               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (id == winrt::guid_of&lt;winrt::IStringable&gt;() &amp;&amp;<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">       !is_stringable_enabled())                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    {                                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        return nullptr;                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return implements::find_interface(id);           <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                    <\/span>\r\n\r\n    \/\/ Implement IWidget methods\r\n    void WidgetMethod();\r\n\r\n    \/\/ Implement IStringable methods\r\n    winrt::hstring ToString();\r\n};\r\n<\/pre>\n<p>When a query comes in for <code>IStringable<\/code>, we check whether <code>IStringable<\/code> support is enabled. If not, then we return <code>nullptr<\/code> immediately, which causes the <code>Query\u00adInterface<\/code> to fail with <code>E_<wbr \/>NO\u00adINTERFACE<\/code>. Otherwise, we forward the call to the base class to continue normally.<\/p>\n<p>As I noted before, according to COM rules, once you decide whether or not <code>IStringable<\/code> is supported, you have to stick with that decision for the lifetime of the object. The imaginary <code>is_<wbr \/>stringable_<wbr \/>enabled()<\/code> function should be based on some immutable state, or at least state which <i>becomes<\/i> immutable once the <code>is_<wbr \/>stringable_<wbr \/>enabled()<\/code> function is called.<\/p>\n<p>Now, there is also a method on <code>IInspectable<\/code> called <code>Get\u00adIids<\/code> which returns a list of the Windows Runtime interfaces implemented by an object. Shouldn&#8217;t we also remove <code>IStringable<\/code> from that list?<\/p>\n<p>I guess you could do that, but it wouldn&#8217;t help much. The <code>Get\u00adIids<\/code> method is used for runtime reflection. However, a much richer way to get the Windows Runtime interfaces implemented by an object is to ask the object for its runtime class name (<code>Get\u00adRuntime\u00adClass\u00adName<\/code>) and then look up that name in the Windows metadata (winmd) file. The winmd file will tell you all the interfaces which the object implements, and it will be in the form of strings, not IIDs. You can then look up those interfaces in Windows metadata files to see what their methods are. On the other hand, <code>Get\u00adIids<\/code> just gives you a bunch of IIDs, and there&#8217;s no practical way to get an interface&#8217;s name and methods from its IID.<\/p>\n<p>Even though we could remove the IID from those reported by <code>Get\u00adIids<\/code>, we can&#8217;t remove it from the interfaces reported by the Windows metadata files, since those are just static data files. We may as well just allow the interface to be reported and fail the query later. A debugger might show the methods on that interface, and then when the developer tries to call them, the call will fail. Not the best experience, but not the end of the world.<\/p>\n<p><b>Bonus chatter<\/b>: A template for this pattern <a href=\"https:\/\/github.com\/microsoft\/wil\/pull\/324\/files\"> exists<\/a> in the <a href=\"https:\/\/github.com\/microsoft\/wil\/\"> Windows Implementation Library<\/a> under the name <a href=\"https:\/\/github.com\/microsoft\/wil\/wiki\/WIL-and-C---WinRT-together#winrt_conditionally_implements\"> <code>wil::<wbr \/>winrt_<wbr \/>conditionally_<wbr \/>implements<\/code><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Prevent <CODE>winrt::implements<\/CODE> from responding to it or reporting 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-108930","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Prevent <CODE>winrt::implements<\/CODE> from responding to it or reporting it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108930","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=108930"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108930\/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=108930"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108930"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108930"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}