{"id":9923,"date":"2011-08-11T07:00:00","date_gmt":"2011-08-11T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2011\/08\/11\/the-ways-people-mess-up-iunknownqueryinterface-episode-4\/"},"modified":"2011-08-11T07:00:00","modified_gmt":"2011-08-11T07:00:00","slug":"the-ways-people-mess-up-iunknownqueryinterface-episode-4","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110811-00\/?p=9923","title":{"rendered":"The ways people mess up IUnknown::QueryInterface, episode 4"},"content":{"rendered":"<p>\nOne of the rules for <code>IUnknown::Query&shy;Interface<\/code> is so\nobvious that nobody even bothers to state it explicitly as a rule:\n&#8220;If somebody asks you for an interface,\nand you return <code>S_OK<\/code>,\nthen the pointer you return must point to the interface the caller\nrequested.&#8221;\n(This feels like the software version of\n<a HREF=\"http:\/\/www.amazon.com\/dp\/0743244753?tag=tholneth-20\">\ndumb warning labels<\/a>.)\n<\/p>\n<p>\nDuring compatibility testing for Windows&nbsp;Vista,\nwe found a shell extension that behaved rather strangely.\nEventually, the problem was traced to\na broken <code>IUnknown::Query&shy;Interface<\/code>\nimplementation which depended subtly on the order in which\ninterfaces were queried.\n<\/p>\n<p>\nThe shell asked for the <code>IExtract&shy;IconA<\/code> and\n<code>IExtract&shy;IconW<\/code> interfaces in the following\norder:\n<\/p>\n<pre>\n\/\/ not the actual code but it gets the point across\nIExtractIconA *pxia;\nIExtractIconW *pxiw;\npunk-&gt;QueryInterface(IID_IExtractIconA, &amp;pxia);\npunk-&gt;QueryInterface(IID_IExtractIconW, &amp;pxiw);\n<\/pre>\n<p>\nOne particular shell extension would return the same pointer to\nboth queries;\n<i>i.e.<\/i>,\nafter the above code executed,\n<code>pxia == pxiw<\/code> even though neither interface\nderived from the other.\nThe two interfaces are not binary-compatible,\nbecause <code>IExtract&shy;IconA::Get&shy;Icon&shy;Location<\/code>\noperates on ANSI strings, whereas\n<code>IExtract&shy;IconW::Get&shy;Icon&shy;Location<\/code> operates\non Unicode strings.\n<\/p>\n<p>\nThe shell called <code>pxiw-&gt;Get&shy;Icon&shy;Location<\/code>,\nbut the object interpreted the <code>szIcon&shy;File<\/code> as\nan ANSI string buffer; as a result, when the shell went to look at it,\nit saw gibberish.\n<\/p>\n<p>\nFurther experimentation revealed that if the order of the two\n<code>Query&shy;Interface<\/code> calls were reversed,\nthen <code>pxiw-&gt;Get&shy;Icon&shy;Location<\/code> worked as expected.\nIn other words,\nthe first interface you requested &#8220;locked&#8221; the object into that\ninterface, and asking for any other interface just returned\na pointer to the locked interface.\nThis struck me as very odd; coding up the object this way\nseems to be <i>harder<\/i> than doing it the right way!\n<\/p>\n<pre>\n<i>\/\/ this code is wrong - see discussion above\nclass CShellExtension : public IExtractIcon\n{\n enum { MODE_UNKNOWN, MODE_ANSI, MODE_UNICODE };\n  HRESULT CShellExtension::QueryInterface(REFIID riid, void **ppv)\n  {\n   *ppv = NULL;\n   if (riid == IID_IUnknown) *ppv = this;\n   else if (riid == IID_IExtractIconA) {\n    if (m_mode == MODE_UNKNOWN) m_mode = MODE_ANSI;\n    *ppv = this;\n   } else if (riid == IID_IExtractIconW) {\n    if (m_mode == MODE_UNKNOWN) m_mode = MODE_UNICODE;\n    *ppv = this;\n   }\n   if (*ppv) AddRef();\n   return *ppv ? S_OK : E_NOINTERFACE;\n  }\n  ... AddRef \/ Release ...\n  HRESULT GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax,\n                          int *piIndex, UINT *pwFlags)\n  {\n   if (m_mode == MODE_ANSI) lstrcpynA((char*)szIconFile, \"foo\", cchMax);\n   else lstrcpynW((WCHAR*)szIconFile, L\"foo\", cchMax);\n   ...\n  }\n  ...\n}<\/i>\n<\/pre>\n<p>\nInstead of implementing both <code>IExtract&shy;IconA<\/code> and\n<code>IExtract&shy;IconW<\/code>, my guess is that they implemented\njust one of the interfaces and made it alter its behavior based\non which interface it thinks it needs to pretend to be.\nIt never occurred to them that the single interface might need\nto pretend to be two different things at the same time.\n<\/p>\n<p>\nThe right way of supporting two interfaces is to actually implement\ntwo interfaces and not write a single morphing interface.\n<\/p>\n<pre>\nclass CShellExtension : public IExtractIconA, public IExtractIconW\n{\n  HRESULT CShellExtension::QueryInterface(REFIID riid, void **ppv)\n  {\n   *ppv = NULL;\n   if (riid == IID_IUnknown ||\n       riid == IID_IExtractIconA) {\n    *ppv = static_cast&lt;IExtractIconA*&gt;(this);\n   } else if (riid == IID_IExtractIconW) {\n    *ppv = static_cast&lt;IExtractIconW*&gt;(this);\n   }\n   if (*ppv) AddRef();\n   return *ppv ? S_OK : E_NOINTERFACE;\n  }\n  ... AddRef \/ Release ...\n  HRESULT GetIconLocation(UINT uFlags, LPSTR szIconFile, UINT cchMax,\n                          int *piIndex, UINT *pwFlags)\n  {\n   lstrcpynA(szIconFile, \"foo\", cchMax);\n   return GetIconLocationCommon(uFlags, piIndex, pwFlags);\n  }\n  HRESULT GetIconLocation(UINT uFlags, LPWSTR szIconFile, UINT cchMax,\n                          int *piIndex, UINT *pwFlags)\n  {\n   lstrcpynW(szIconFile, L\"foo\", cchMax);\n   return GetIconLocationCommon(uFlags, piIndex, pwFlags);\n  }\n  ...\n}<\/i>\n<\/pre>\n<p>\nWe worked around this in the shell by simply changing the order\nin which we perform the calls to\n<code>IUnknown::Query&shy;Interface<\/code>\nand adding a comment explaining why the order of the calls is important.\n<\/p>\n<p>\n(This is another example of how the cost of a compatibility fix\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2007\/07\/23\/4003873.aspx\">\nis small potatoes<\/a>.\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2010\/01\/11\/9946339.aspx\">\nThe cost of deciding whether or not to apply the fix far exceeds\nthe cost of just doing it for everybody<\/a>.)\n<\/p>\n<p>\nA different shell extension had a compatibility problem that also was\ntraced back to a dependency on the order in which the shell\nasked for interfaces.\nThe shell extension registered as a context menu extension,\nbut when the shell tried to create it, it got <code>E_NO&shy;INTERFACE<\/code>\nback:\n<\/p>\n<pre>\nCoCreateInstance(CLSID_YourAwesomeExtension, NULL,\n                 CLSCTX_INPROC_SERVER, IID_IContextMenu, &amp;pcm);\n\/\/ returns E_NOINTERFACE?\n<\/pre>\n<p>\nThis was kind of bizarre.\nI mean, the shell extension went to the effort of registering itself\nas a context menu extension,\nbut when the shell said,\n&#8220;Okay, it&#8217;s show time, let&#8217;s do the context menu dance!&#8221;\nit replied,\n&#8220;Sorry, I don&#8217;t do that.&#8221;\n<\/p>\n<p>\nThe vendor explained that the shell extension relies\non the order in which the shell asked for interfaces.\nThe shell used to create and initialize the extension like this:\n<\/p>\n<pre>\n\/\/ error checking and other random bookkeeping removed\nIShellExtInit *psei;\nIContextMenu *pcm;\nCoCreateInstance(CLSID_YourAwesomeExtension, NULL,\n                 CLSCTX_INPROC_SERVER, IID_IShellExtInit, &amp;psei);\npsei-&gt;Initialize(...);\npsei-&gt;QueryInterface(IID_IContextMenu, &amp;pcm);\npsei-&gt;Release();\n\/\/ use pcm\n<\/pre>\n<p>\nWe changed the order in a manner that you would think should be equivalent:\n<\/p>\n<pre>\nCoCreateInstance(CLSID_YourAwesomeExtension, NULL,\n                 CLSCTX_INPROC_SERVER, IID_IContextMenu, &amp;pcm);\npcm-&gt;QueryInterface(IID_IShellExtInit, &amp;psei);\npsei-&gt;Initialize(...);\npsei-&gt;Release();\n<\/pre>\n<p>\n(Of course, it&#8217;s not written in so many words in the code;\nthe various parts are spread out over different components and\nhelper functions,\nbut this is the sequence of calls the shell extension sees.)\n<\/p>\n<p>\nThe vendor explained that their shell extension will not respond\nto any shell extension interfaces (aside from\n<code>IShell&shy;Ext&shy;Init<\/code>)\nuntil it has been initialized,\nbecause it is at that point that they decide which extensions\nthey want to support.\nUnfortunately, this violates the first of the\n<a HREF=\"http:\/\/msdn.microsoft.com\/ms682521.aspx\">\nfour explicit rules for <code>IUnknown::Query&shy;Interface<\/code><\/a>,\nnamely that the set of interfaces must be static.\n(It&#8217;s okay to have an object expose different interfaces\nconditionally, as long as it understands that once it says yes or no\nto a particular interface,\nit is committed to answering the same way for the lifetime of the object.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the rules for IUnknown::Query&shy;Interface is so obvious that nobody even bothers to state it explicitly as a rule: &#8220;If somebody asks you for an interface, and you return S_OK, then the pointer you return must point to the interface the caller requested.&#8221; (This feels like the software version of dumb warning labels.) During [&hellip;]<\/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-9923","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>One of the rules for IUnknown::Query&shy;Interface is so obvious that nobody even bothers to state it explicitly as a rule: &#8220;If somebody asks you for an interface, and you return S_OK, then the pointer you return must point to the interface the caller requested.&#8221; (This feels like the software version of dumb warning labels.) During [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9923","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=9923"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9923\/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=9923"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=9923"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=9923"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}