{"id":104406,"date":"2020-10-29T07:00:00","date_gmt":"2020-10-29T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104406"},"modified":"2020-10-28T21:59:33","modified_gmt":"2020-10-29T04:59:33","slug":"20201029-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201029-00\/?p=104406","title":{"rendered":"Do any Windows Runtime projections cache nondefault Windows Runtime interfaces?"},"content":{"rendered":"<p>Some time ago, I discussed <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200324-00\/?p=103586\"> how Windows Runtime language projections call methods on nondefault interfaces<\/a>. Do any projections cache nondefault interfaces?<\/p>\n<p>The C++\/CX and C++\/WinRT projections represent Windows Runtime objects as a reference-counted pointer to the object&#8217;s default interface. As a result, there&#8217;s nowhere to cache the nondefault interfaces, since the only storage available is the pointer itself.\u00b9<\/p>\n<p>The C# and JavaScript projections wrap the Windows Runtime inside native C# and JavaScript objects, and in that case, there&#8217;s plenty of room to do things like cache nondefault interfaces in that native object.<\/p>\n<p>If C++\/CX or C++\/WinRT wanted to break the rule that their projections are just wrappers around a single pointer, they could have made the projection be its own object. One option would be to have it passed by value:<\/p>\n<pre>class Thing\r\n{\r\nprivate:\r\n  ComPtr&lt;ABI::IThing&gt; thing;\r\n  ComPtr&lt;ABI::IThing2&gt; mutable thing2;\r\n\r\npublic:\r\n  \/\/ Method on default interface IThing\r\n  void Method1() const\r\n  {\r\n    ThrowIfFailed(thing-&gt;Method1());\r\n  }\r\n\r\n  \/\/ Method on nondefault interface IThing2\r\n  void Method2() const\r\n  {\r\n    \/\/ Ignoring thread-safety for expository simplicity\r\n    if (!thing2) {\r\n      ThrowIfFailed(thing-&gt;\r\n        QueryInterface(IID_PPV_ARGS(&amp;thing2)));\r\n    }\r\n    ThrowIfFailed(thing2-&gt;Method2());\r\n  }\r\n\r\n  bool operator==(Thing const&amp; other) const noexcept\r\n  {\r\n    return thing == other.thing;\r\n  }\r\n};\r\n<\/pre>\n<p>The downside of this implementation is that the object is now larger than a pointer, so it gets more expensive to pass around. The projection would become very large for a class like <code>UIElement<\/code> with seventeen interfaces. If you wanted to copy the cache when the object is copied, that&#8217;s potentially sixteen extra <code>Add\u00adRef<\/code> calls when the parameter is passed. And regardless, you have up to sixteen extra <code>Release<\/code> calls when the parameter is destroyed.<\/p>\n<p>Another option is to make the projection similar to the C# and JavaScript projections and have the projected object be a reference to a hidden wrapper.<\/p>\n<pre>class ThingImpl\r\n{\r\nprivate:\r\n  ComPtr&lt;ABI::IThing&gt; thing;\r\n  ComPtr&lt;ABI::IThing2&gt; mutable thing2;\r\n\r\npublic:\r\n  \/\/ Method on default interface IThing\r\n  void Method1() const\r\n  {\r\n    ThrowIfFailed(thing-&gt;Method1());\r\n  }\r\n\r\n  \/\/ Method on nondefault interface IThing2\r\n  void Method2() const\r\n  {\r\n    \/\/ Ignoring thread-safety for expository simplicity\r\n    if (!thing2) {\r\n      ThrowIfFailed(thing-&gt;\r\n        QueryInterface(IID_PPV_ARGS(&amp;thing2)));\r\n    }\r\n    ThrowIfFailed(thing2-&gt;Method2());\r\n  }\r\n};\r\n\r\nclass Thing\r\n{\r\nprivate:\r\n  std::shared_ptr&lt;ThingImpl&gt; impl;\r\n\r\n  IThing* get_raw_pointer() const\r\n  {\r\n    return impl ? impl.get()-&gt;thing : nullptr;\r\n  }\r\n\r\npublic:\r\n  void Method1() const { return impl-&gt;Method1(); }\r\n  void Method2() const { return impl-&gt;Method2(); }\r\n\r\n  bool operator==(Thing const&amp; other) const noexcept\r\n  {\r\n    return get_raw_pointer() == other.get_raw_pointer());\r\n  }\r\n};\r\n<\/pre>\n<p>In this case, the projected object is just a <code>std::shared_ptr<\/code> to a shared cache. This copies relatively quickly, since it&#8217;s just bumping a reference count on the control block. The downside is that it costs an extra allocation each time a new wrapper is created.\u00b2 (Copying a wrapper just copies the inner <code>shared_ptr<\/code>, but creating a new wrapper requires the creation of a new <code>shared_ptr<\/code>.)<\/p>\n<p>C++\/CX and C++\/WinRT chose to make the projected object be a direct pointer to the Windows Runtime object&#8217;s default interface. It&#8217;s smaller, avoids extra allocations, and makes converting between projected and ABI types simpler. The cost is that members of nondefault interfaces are more expensive.<\/p>\n<p>\u00b9 Even if the cached interface were derived from the default interface (which doesn&#8217;t happen in the Windows Runtime, but work with me here), you couldn&#8217;t &#8220;upgrade&#8221; the pointer to the derived interface because you would have no way of knowing later whether that pointer is a boring default interface or an upgraded cached interface.<\/p>\n<p>\u00b2 C# (and I&#8217;m guessing probably JavaScript) will reuse a wrapper when it needs to wrap a Windows Runtime object and finds that a wrapper already exists. The C++ projections could have done this, noting that the lookup table would have to be per-module.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Possibly, but I know two that definitely don&#8217;t.<\/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-104406","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Possibly, but I know two that definitely don&#8217;t.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104406","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=104406"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104406\/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=104406"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104406"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104406"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}