{"id":106353,"date":"2022-03-16T07:00:00","date_gmt":"2022-03-16T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106353"},"modified":"2023-11-12T17:28:32","modified_gmt":"2023-11-13T01:28:32","slug":"20220316-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220316-00\/?p=106353","title":{"rendered":"Reducing chattiness by querying for multiple interfaces at once, part 2"},"content":{"rendered":"<p>Last time, we saw how we can <a title=\"Reducing chattiness by querying for multiple interfaces at once, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220315-00\/?p=106350\"> use <code>MULTI_QI<\/code> to get multiple interfaces packed into a single server call<\/a>. This saves us from having to issue separate <code>Query\u00adInterface<\/code> calls, avoiding a round-trip call to the server for each interface.<\/p>\n<p>It was relatively easy for us to do this in our sample because all of the <code>Query\u00adInterface<\/code> calls were in the same function. But what if they are spread out?<\/p>\n<pre>\/\/ Error checking elided for expository purposes.\r\n\r\nvoid Gadget::DoSomethingWithWidget()\r\n{\r\n    \/\/ Create a widget.\r\n    wil::com_ptr&lt;IWidget&gt; widget;\r\n    CoCreateInstance(CLSID_Widget, nullptr,\r\n        CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&amp;widget));\r\n\r\n    LoadWidgetWithSite(widget.get(), site, m_fileName);\r\n\r\n    \/\/ Ready to do widget things.\r\n    widget-&gt;DoSomething();\r\n}\r\n\r\nvoid LoadWidgetWithSite(IWidget* widget,\r\n    IUnknown* site, PCWSTR fileName)\r\n{\r\n    \/\/ Set the site.\r\n    wil::com_ptr&lt;IObjectWithSite&gt; objectWithSite;\r\n    widget-&gt;QueryInterface(IID_PPV_ARGS(&amp;objectWithSite));\r\n    objectWithSite-&gt;SetSite(site);\r\n\r\n    \/\/ Load it from the file.\r\n    wil::com_ptr&lt;IPersistFile&gt; persistFile;\r\n    widget-&gt;QueryInterface(IID_PPV_ARGS(&amp;persistFile));\r\n    persistFile-&gt;Load(fileName, STGM_READ);\r\n}\r\n<\/pre>\n<p>If we want to take advantage of <code>Co\u00adCreate\u00adInstance\u00adEx<\/code> and <code>MULTI_QI<\/code>, it looks like we&#8217;ll have to pass those pre-queried interfaces to <code>Load\u00adWidget\u00adWith\u00adSite<\/code>, which makes it more unwieldy:<\/p>\n<pre>\/\/ Error checking elided for expository purposes.\r\n\r\nvoid Gadget::DoSomethingWithWidget()\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ Create a widget and prefetch the interfaces.<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">MULTI_QI mqi[3] = {                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    { &amp;__uuidof(IWidget), nullptr, 0 },        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    { &amp;__uuidof(IObjectWithSite), nullptr, 0 },<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    { &amp;__uuidof(IPersistFile), nullptr, 0 },   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">};                                             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">HRESULT hr = CoCreateInstanceEx(               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    CLSID_Widget, nullptr, CLSCTX_LOCAL_SERVER,<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    nullptr, 3, mqi);                          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">wil::com_ptr&lt;IWidget&gt; widget;                  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">widget.attach(mqi[0].pItf);                    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">wil::com_ptr&lt;IObjectWithSite&gt; objectWithSite;  <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">objectWithSite.attach(mqi[1].pItf);            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">wil::com_ptr&lt;IPersistFile&gt; persistFile;        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">persistFile.attach(mqi[2].pItf);               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">if (hr != S_OK) {                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    \/\/ Failed to get at least one interface.   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return;                                    <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                              <\/span>\r\n\r\n    LoadWidgetWithSite(widget.get(),\r\n        <span style=\"border: solid 1px currentcolor;\">objectWithSite.get()<\/span>, site,\r\n        <span style=\"border: solid 1px currentcolor;\">persistFile.get()<\/span>, m_fileName);\r\n\r\n    \/\/ Ready to do widget things.\r\n    widget-&gt;DoSomething();\r\n}\r\n\r\nvoid LoadWidgetWithSite(IWidget* widget,\r\n    <span style=\"border: solid 1px currentcolor;\">IObjectWithSite* objectWithSite<\/span>, IUnknown* site,\r\n    <span style=\"border: solid 1px currentcolor;\">IPersistFile* persistFile<\/span>, PCWSTR fileName)\r\n{\r\n    \/\/ Set the site.\r\n    objectWithSite-&gt;SetSite(site);\r\n\r\n    \/\/ Load it from the file.\r\n    persistFile-&gt;Load(fileName, STGM_READ);\r\n}\r\n<\/pre>\n<p>But it turns out that you don&#8217;t have to rewrite all of your methods. All you have to do is prefetch the interfaces and <i>throw them away<\/i>!<\/p>\n<pre>\/\/ Error checking elided for expository purposes.\r\n\r\nvoid Gadget::DoSomethingWithWidget()\r\n{\r\n    \/\/ Create a widget and prefetch the interfaces.\r\n    MULTI_QI mqi[3] = {\r\n        { &amp;__uuidof(IWidget), nullptr, 0 },\r\n        { &amp;__uuidof(IObjectWithSite), nullptr, 0 },\r\n        { &amp;__uuidof(IPersistFile), nullptr, 0 },\r\n    };\r\n    HRESULT hr = CoCreateInstanceEx(\r\n        CLSID_Widget, nullptr, CLSCTX_LOCAL_SERVER,\r\n        nullptr, 3, mqi);\r\n\r\n    wil::com_ptr&lt;IWidget&gt; widget;\r\n    widget.attach(mqi[0].pItf);\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (mqi[1].pItf) mqi[1].pItf-&gt;Release();<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">if (mqi[2].pItf) mqi[2].pItf-&gt;Release();<\/span>\r\n\r\n    if (hr != S_OK) {\r\n        \/\/ Failed to get at least one interface.\r\n        return;\r\n    }\r\n\r\n    \/\/ The rest is the same as the non-MULTI_QI version.\r\n    LoadWidgetWithSite(widget.get(), site, m_fileName);\r\n\r\n    \/\/ Ready to do widget things.\r\n    widget-&gt;DoSomething();\r\n}\r\n\r\nvoid LoadWidgetWithSite(IWidget* widget,\r\n    IUnknown* site, PCWSTR fileName)\r\n{\r\n    \/\/ Set the site.\r\n    wil::com_ptr&lt;IObjectWithSite&gt; objectWithSite;\r\n    widget-&gt;QueryInterface(IID_PPV_ARGS(&amp;objectWithSite));\r\n    objectWithSite-&gt;SetSite(site);\r\n\r\n    \/\/ Load it from the file.\r\n    wil::com_ptr&lt;IPersistFile&gt; persistFile;\r\n    widget-&gt;QueryInterface(IID_PPV_ARGS(&amp;persistFile));\r\n    persistFile-&gt;Load(fileName, STGM_READ);\r\n}\r\n<\/pre>\n<p>Even though we threw the prefetched interfaces away, they have been cached in the proxy, and future calls to <code>Query\u00adInterface<\/code> will return the cached value instead of sending a call all the way back out to the server.<\/p>\n<p>The proxy also caches negative results, so if we had an optional interface, the proxy will remember that a query for that interface failed in the past, so when you ask for it again, it will return the error from the earlier <code>Query\u00adInterface<\/code> without going to the server.<\/p>\n<p>The rules for <code>IUnknown<\/code> regarding interface stability ensure that it is valid to cache the results of earlier <code>Query\u00adInterface<\/code> calls.\u00b9<\/p>\n<p>Even the <code>Release<\/code> calls on the interfaces won&#8217;t result in a call out to the server: Only the final <code>Release<\/code> of a proxy results in a call to the server, and we still have an active reference in <code>widget<\/code>. (We took advantage of this behavior <a title=\"COM asynchronous interfaces, part 8: Asynchronous release, the problems\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220223-00\/?p=106282\"> a little while ago<\/a>.)<\/p>\n<p>Mind you, the <code>MULTI_QI<\/code> structure is rather awkward to manage. Maybe we can use some C++ magic to make it easier. We&#8217;ll look at that next time.<\/p>\n<p>\u00b9 You might say that <code>Query\u00adInterface<\/code> &#8220;collapses the wave function&#8221; for interface detection. If your object is never asked &#8220;Do you support <code>IWidget<\/code>?&#8221; then it can exist in a quantum superposition state of &#8220;supports <code>IWidget<\/code>\/doesn&#8217;t support <code>IWidget<\/code>.&#8221; But once somebody asks, the object must decide which way it wants to be, and has to stick with that decision for the remainder of its lifetime.<\/p>\n<p>You can take advantage of the &#8220;quantum superposition state&#8221; in your objects. For example, your <code>Tool<\/code> object that might be an <code>IWidget<\/code>, or it might be an <code>IGadget<\/code>, depending on how it is configured. The client can reconfigure the Tool all it wants, but once it asks &#8220;Did I make a Widget?&#8221;, the configuration is locked in. In practice, you see this pattern in the cases where the configuration is done by something like <code>IPersistStream<\/code>.<\/p>\n<pre>wil::com_ptr&lt;IPersistStream&gt; persist;\r\nCoCreateInstance(CLSID_Tool, ..., IID_PPV_ARGS(&amp;persist));\r\n\r\n\/\/ The tool doesn't know what it is yet.\r\n\r\npersist-&gt;Load(stream);\r\n\r\n\/\/ The tool can delay the decision until somebody finally asks,\r\n\/\/ \"Are you a Widget?\"\r\n\r\nwil::com_ptr&lt;IWidget&gt; widget;\r\npersist-&gt;QueryInterface(IID_PPV_ARGS(&amp;widget));\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>You can just load them into the cache.<\/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-106353","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can just load them into the cache.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106353","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=106353"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106353\/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=106353"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106353"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106353"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}