{"id":37643,"date":"2004-10-06T07:00:00","date_gmt":"2004-10-06T14:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2004\/10\/06\/how-to-host-an-icontextmenu-part-10-composite-extensions-groundwork\/"},"modified":"2004-10-06T07:00:00","modified_gmt":"2004-10-06T14:00:00","slug":"how-to-host-an-icontextmenu-part-10-composite-extensions-groundwork","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20041006-00\/?p=37643","title":{"rendered":"How to host an IContextMenu, part 10 &#8211; Composite extensions &#8211; groundwork"},"content":{"rendered":"<p><P>\nYou might wonder why\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/shell\/reference\/ifaces\/icontextmenu\/icontextmenu.asp\">\nthe <CODE>IContextMenu<\/CODE> interface<\/A>\noperates on menu identifier offsets so much\nrather than with the menu identifiers themselves.\n<\/P>\n<P>\nThe reason is to support something which I will call &#8220;compositing&#8221;.\n<\/P>\n<P>\nYou may have multiple context menu extensions that you want to\ncombine into one giant context menu extension.  The shell does this\nall over the place.  For example, the context menu we have been\nplaying with all this time is really a composite of several\nindividual context menu extensions: the static registry verbs\nplus all the COM-based extensions like &#8220;Send To&#8221;, &#8220;Open With&#8221;,\nand anything else that may have been added by a program you installed\n(like a virus checker).\n<\/P>\n<P>\nSo before we can write a compositor, we need to \nhave a second context menu to composite.  Here&#8217;s a quickie that\nimplements two commands, let&#8217;s call them &#8220;Top&#8221; and &#8220;Next&#8221; for lack\nof anything interesting to do.\n<\/P>\n<PRE>\nclass CTopContextMenu : public IContextMenu\n{\npublic:\n  \/\/ *** IUnknown ***\n  STDMETHODIMP QueryInterface(REFIID riid, void **ppv);\n  STDMETHODIMP_(ULONG) AddRef();\n  STDMETHODIMP_(ULONG) Release();<\/p>\n<p>  \/\/ *** IContextMenu ***\n  STDMETHODIMP QueryContextMenu(HMENU hmenu,\n                          UINT indexMenu, UINT idCmdFirst,\n                          UINT idCmdLast, UINT uFlags);\n  STDMETHODIMP InvokeCommand(\n                          LPCMINVOKECOMMANDINFO lpici);\n  STDMETHODIMP GetCommandString(\n                          UINT_PTR    idCmd,\n                          UINT        uType,\n                          UINT      * pwReserved,\n                          LPSTR       pszName,\n                          UINT        cchMax);<\/p>\n<p>  static HRESULT Create(REFIID riid, void **ppv);<\/p>\n<p>private:\n  CTopContextMenu() : m_cRef(1), m_cids(0) { }<\/p>\n<p>private:\n  HRESULT ValidateCommand(UINT_PTR idCmd, BOOL fUnicode,\n                          UINT *puOffset);\n  HRESULT Top(LPCMINVOKECOMMANDINFO lpici);\n  HRESULT Next(LPCMINVOKECOMMANDINFO lpici);<\/p>\n<p>private:\n  ULONG m_cRef;\n  UINT  m_cids;\n};\n<\/PRE>\n<P>\nThe class declaration isn&#8217;t particularly interesting.  We\nare not owner-draw so we don&#8217;t bother implementing\n<CODE><A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/shell\/reference\/ifaces\/icontextmenu2\/icontextmenu2.asp\">IContextMenu2<\/A><\/CODE> or\n<CODE><A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/shell\/reference\/ifaces\/icontextmenu3\/icontextmenu3.asp\">IContextMenu3<\/A><\/CODE>.\n<\/P>\n<P>\nFirst, some basic paperwork for getting off the ground.\n<PRE>\nHRESULT CTopContextMenu::Create(REFIID riid, void **ppv)\n{\n  *ppv = NULL;\n  HRESULT hr;\n  CTopContextMenu *self = new CTopContextMenu();\n  if (self) {\n    hr = self-&gt;QueryInterface(riid, ppv);\n    self-&gt;Release();\n  } else {\n    hr = E_OUTOFMEMORY;\n  }\n  return hr;\n}\n<\/PRE>\n<P>\nWe have two commands. Instead of hard-coding the numbers\n<CODE>0<\/CODE> and <CODE>1<\/CODE>,\nlet&#8217;s give them nice names.\n<\/P>\n<PRE>\n#define TOPCMD_TOP      0\n#define TOPCMD_NEXT     1\n#define TOPCMD_MAX      2\n<\/PRE>\n<P>\nAnd here&#8217;s a table that we&#8217;re going to use to help us manage\nthese two commands.\n<\/P>\n<PRE>\nconst struct COMMANDINFO {\n  LPCSTR  pszNameA;\n  LPCWSTR pszNameW;\n  LPCSTR  pszHelpA;\n  LPCWSTR pszHelpW;\n} c_rgciTop[] = {\n  { &#8220;top&#8221;,  L&#8221;top&#8221;,\n    &#8220;The top command&#8221;,  L&#8221;The top command&#8221;, }, \/\/ TOPCMD_TOP\n  { &#8220;next&#8221;, L&#8221;next&#8221;,\n    &#8220;The next command&#8221;, L&#8221;The next command&#8221;, },\/\/ TOPCMD_NEXT\n};\n<\/PRE>\n<P>\nOur <CODE>TOPCMD_*<\/CODE>\nvalues conveniently double as indices into the\n<CODE>c_rgciTop<\/CODE> array.\n<\/P>\n<P>\nNext come the boring parts of a COM object:\n<\/P>\n<PRE>\nHRESULT CTopContextMenu::QueryInterface(REFIID riid, void **ppv)\n{\n  IUnknown *punk = NULL;\n  if (riid == IID_IUnknown) {\n    punk = static_cast&lt;IUnknown*&gt;(this);\n  } else if (riid == IID_IContextMenu) {\n    punk = static_cast&lt;IContextMenu*&gt;(this);\n  }<\/p>\n<p>  *ppv = punk;\n  if (punk) {\n    punk-&gt;AddRef();\n    return S_OK;\n  } else {\n    return E_NOINTERFACE;\n  }\n}<\/p>\n<p>ULONG CTopContextMenu::AddRef()\n{\n  return ++m_cRef;\n}<\/p>\n<p>ULONG CTopContextMenu::Release()\n{\n  ULONG cRef = &#8211;m_cRef;\n  if (cRef == 0) delete this;\n  return cRef;\n}\n<\/PRE>\n<P>\nFinally, we get to something interesting:\n<CODE><A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/shell\/reference\/ifaces\/icontextmenu\/querycontextmenu.asp\">IContextMenu::QueryContextMenu<\/A><\/CODE>.\nThings to watch out for in the code below:\n<\/P>\n<UL>\n<LI>Checking whether there is room between\n    <CODE>idCmdFirst<\/CODE> and\n    <CODE>idCmdLast<\/CODE> is complicated by the fact that\n    <CODE>idCmdLast<\/CODE> is\n    endpoint-<STRONG>inclusive<\/STRONG>, which forces a strange <CODE>+1<\/CODE>.\n    Another reason to\n    <A HREF=\"\/oldnewthing\/archive\/2004\/02\/18\/75652.aspx\">\n    prefer endpoint-exclusive ranges<\/A>.\n<LI>If the <CODE>CMF_DEFAULTONLY<\/CODE> flag is set, then we don&#8217;t bother\n    adding our menu items since none of our options is the default\n    menu item.\n<\/UL>\n<PRE>\nHRESULT CTopContextMenu::QueryContextMenu(\n    HMENU hmenu, UINT indexMenu, UINT idCmdFirst,\n    UINT idCmdLast, UINT uFlags)\n{\n  m_cids = 0;<\/p>\n<p>  if ((int)(idCmdLast &#8211; idCmdFirst + 1) &gt;= TOPCMD_MAX &amp;&amp;\n    !(uFlags &amp; CMF_DEFAULTONLY)) {\n    InsertMenu(hmenu, indexMenu + TOPCMD_TOP, MF_BYPOSITION,\n               idCmdFirst + TOPCMD_TOP, TEXT(&#8220;Top&#8221;));\n    InsertMenu(hmenu, indexMenu + TOPCMD_NEXT, MF_BYPOSITION,\n               idCmdFirst + TOPCMD_NEXT, TEXT(&#8220;Next&#8221;));\n    m_cids = TOPCMD_MAX;\n  }<\/p>\n<p>  return MAKE_HRESULT(SEVERITY_SUCCESS, 0, m_cids);\n}\n<\/PRE>\n<P>\nIn order to implement the next few methods, we need to have\nsome culture-invariant comparison functions.\n<\/P>\n<PRE>\nint strcmpiA_invariant(LPCSTR psz1, LPCSTR psz2)\n{\n  return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,\n                        psz1, -1, psz2, -1) &#8211; CSTR_EQUAL;\n}<\/p>\n<p>int strcmpiW_invariant(LPCWSTR psz1, LPCWSTR psz2)\n{\n  return CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE,\n                        psz1, -1, psz2, -1) &#8211; CSTR_EQUAL;\n}\n<\/PRE>\n<P>\nThese are like the strcmpi functions except that they\nuse the invariant locale since they will be used to compare\ncanonical strings rather than strings that are meaningful to\nan end user.\n(<A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/winui\/winui\/windowsuserinterface\/resources\/strings\/stringreference\/stringfunctions\/comparestring.asp\">More discussion here in MSDN<\/A>.)\n<\/P>\n<P>\nNow we have enough to write a helper function which is\ncentral to the context menu: Figuring out which command\nsomebody is talking about.\n<\/P>\n<P>\nCommands can be passed to the <CODE>IContextMenu<\/CODE> interface either\n(a)&nbsp;by ordinal or by name, and either (b)&nbsp;as ANSI or\nas Unicode.  This counts as either three ways or four ways,\ndepending on whether you treat &#8220;ANSI as ordinal&#8221; and &#8220;Unicode\nas ordinal&#8221; as the same thing or not.\n<\/P>\n<PRE>\nHRESULT CTopContextMenu::ValidateCommand(UINT_PTR idCmd,\n                        BOOL fUnicode, UINT *puOffset)\n{\n  if (!IS_INTRESOURCE(idCmd)) {\n    if (fUnicode) {\n      LPCWSTR pszMatch = (LPCWSTR)idCmd;\n      for (idCmd = 0; idCmd &lt; TOPCMD_MAX; idCmd++) {\n        if (strcmpiW_invariant(pszMatch,\n                               c_rgciTop[idCmd].pszNameW) == 0) {\n          break;\n        }\n      }\n    } else {\n      LPCSTR pszMatch = (LPCSTR)idCmd;\n      for (idCmd = 0; idCmd &lt; TOPCMD_MAX; idCmd++) {\n        if (strcmpiA_invariant(pszMatch,\n                               c_rgciTop[idCmd].pszNameA) == 0) {\n          break;\n        }\n      }\n    }\n  }<\/p>\n<p>  if (idCmd &lt; m_cids) {\n    *puOffset = (UINT)idCmd;\n    return S_OK;\n  }<\/p>\n<p>  return E_INVALIDARG;\n}\n<\/PRE>\n<P>\nThis helper function takes a &#8220;something&#8221; parameter in the\nform of a <CODE>UINT_PTR<\/CODE> and a flag that indicates whether that\n&#8220;something&#8221; is ANSI or Unicode.  The function itself checks\nwhether the &#8220;something&#8221; is a string or an ordinal.  If a string,\nthen it converts that string into an ordinal by looking for\nit in the table of commands in the appropriate character set\nand using a locale-insensitive comparison.\nNotice that if the string is not found, then\n<CODE>idCmd<\/CODE> is left equal to <CODE>TOPCMD_MAX<\/CODE>,\nwhich is an invalid value (and therefore is neatly handled\nby the fall-through).\n<\/P>\n<P>\nAfter the (possibly failed) conversion to an ordinal, the\nordinal is checked for validity; if valid, then the ordinal\nis returned back for further processing.\n<\/P>\n<P>\nWith this helper function the implementation of the other methods\nof the <CODE>IContextMenu<\/CODE> interface are a lot easier.\n<\/P>\n<P>\nWe continue with the <CODE>IContextMenu::InvokeCommand<\/CODE> method:\n<PRE>\nHRESULT CTopContextMenu::InvokeCommand(\n                            LPCMINVOKECOMMANDINFO lpici) {<\/p>\n<p>  CMINVOKECOMMANDINFOEX* lpicix = (CMINVOKECOMMANDINFOEX*)lpici;\n  BOOL fUnicode = lpici-&gt;cbSize &gt;= sizeof(CMINVOKECOMMANDINFOEX) &amp;&amp;\n                  (lpici-&gt;fMask &amp; CMIC_MASK_UNICODE);\n  UINT idCmd;\n  HRESULT hr = ValidateCommand(fUnicode ? (UINT_PTR)lpicix-&gt;lpVerbW\n                                        : (UINT_PTR)lpici-&gt;lpVerb,\n                               fUnicode, &amp;idCmd);\n  if (SUCCEEDED(hr)) {\n    switch (idCmd) {\n    case TOPCMD_TOP: hr = Top(lpici); break;\n    case TOPCMD_NEXT: hr = Next(lpici); break;\n    default: hr = E_INVALIDARG; break;\n    }\n  }\n  return hr;\n}\n<\/PRE>\n<P>\nHere is a case where the &#8220;Are there three cases or four?&#8221; question\nlands squarely on the side of &#8220;four&#8221;.  There are two forms of the\n<CODE>CMINVOKECOMMANDINFO<\/CODE> structure, the base structure (which is\nANSI-only) and the extended structure\n<CODE>CMINVOKECOMMANDINFOEX<\/CODE> which adds Unicode support.\n<\/P>\n<P>\nIf the structure is <CODE>CMINVOKECOMMANDINFOEX<\/CODE> and the\n<CODE>CMIC_MASK_UNICODE<\/CODE> flag is set, then the Unicode fields\nof the <CODE>CMINVOKECOMMANDINFOEX<\/CODE> structure should be used\nin preference to the ANSI ones.\n<\/P>\n<P>\nThis means that there are indeed four scenarios:\n<\/P>\n<OL>\n<LI>ANSI string in <CODE>lpVerb<\/CODE> member.\n<LI>Ordinal in <CODE>lpVerb<\/CODE> member.\n<LI>Unicode string in <CODE>lpVerbW<\/CODE> member.\n<LI>Ordinal in <CODE>lpVerbW<\/CODE> member.\n<\/OL>\n<P>\nAfter figuring out whether the parameter is ANSI or Unicode,\nwe ask <CODE>ValidateCommand<\/CODE> to do the work of validating\nthe verb and converting it to an ordinal, at which point we use\nthe ordinal in a <CODE>switch<\/CODE> statement to dispatch the\nactual operation.\n<\/P>\n<P>\nFailing to implement string-based command invocation is an\nextremely common oversight in context menu implementations.\nDoing so prevents people from invoking\nyour verbs programmatically.\n<\/P>\n<P>\n&#8220;Why should I bother to let\npeople invoke my verbs programmatically?&#8221;\n<\/P>\n<P>\nBecause if you don&#8217;t,\nthen people won&#8217;t be able to write programs like the one we are\ndeveloping in this series of articles!  For example, suppose\nyour context menu extension lets people &#8220;Frob&#8221; a file.  If you\ndon&#8217;t expose this verb programmability, then it is impossible to write\na program that, say, takes all the files modified in the last\ntwenty-four hours and Frobs them.\n<\/P>\n<P>\n(I&#8217;m always amused by the people who complain\nthat Explorer doesn&#8217;t expose enough customizability\nprogrammatically,\nwhile simultaneously not providing the same degree of\nprogrammatic customizability\nin their own programs.)\n<\/P>\n<P>\nOh wait, I guess I should implement those two operations.\nThey don&#8217;t do anything particularly interesting.\n<\/P>\n<PRE>\nHRESULT CTopContextMenu::Top(LPCMINVOKECOMMANDINFO lpici)\n{\n  MessageBox(lpici-&gt;hwnd, TEXT(&#8220;Top&#8221;), TEXT(&#8220;Title&#8221;), MB_OK);\n  return S_OK;\n}<\/p>\n<p>HRESULT CTopContextMenu::Next(LPCMINVOKECOMMANDINFO lpici)\n{\n  MessageBox(lpici-&gt;hwnd, TEXT(&#8220;Next&#8221;), TEXT(&#8220;Title&#8221;), MB_OK);\n  return S_OK;\n}\n<\/PRE>\n<P>\nThe remaining method is\n<CODE>IContextMenu::GetCommandString<\/CODE>, which is probably\nthe one people most frequently get wrong since the consequences of\ngetting it wrong are not immediately visible to the implementor.\nIt is the people who are trying to access the context menu\nprogrammatically who most likely to notice that the method isn&#8217;t\nworking properly.\n<\/P>\n<PRE>\nHRESULT CTopContextMenu::GetCommandString(\n                            UINT_PTR    idCmd,\n                            UINT        uType,\n                            UINT      * pwReserved,\n                            LPSTR       pszName,\n                            UINT        cchMax)\n{\n  UINT id;\n  HRESULT hr = ValidateCommand(idCmd, uType &amp; GCS_UNICODE, &amp;id);\n  if (FAILED(hr)) {\n    if (uType == GCS_VALIDATEA || uType == GCS_VALIDATEW) {\n      hr = S_FALSE;\n    }\n    return hr;\n  }<\/p>\n<p>  switch (uType) {\n  case GCS_VERBA:\n    lstrcpynA(pszName, c_rgciTop[id].pszNameA, cchMax);\n    return S_OK;<\/p>\n<p>  case GCS_VERBW:\n    lstrcpynW((LPWSTR)pszName, c_rgciTop[id].pszNameW, cchMax);\n    return S_OK;<\/p>\n<p>  case GCS_HELPTEXTA:\n    lstrcpynA(pszName, c_rgciTop[id].pszHelpA, cchMax);\n    return S_OK;<\/p>\n<p>  case GCS_HELPTEXTW:\n    lstrcpynW((LPWSTR)pszName, c_rgciTop[id].pszHelpW, cchMax);\n    return S_OK;<\/p>\n<p>  case GCS_VALIDATEA:\n  case GCS_VALIDATEW:\n    return S_OK;    \/\/ all they wanted was validation\n  }<\/p>\n<p>  return E_NOTIMPL;\n}\n<\/PRE>\n<P>\nHere again we use the\n<CODE>ValidateCommand<\/CODE> method to do the hard work\nof validating the command, which is passed in the\n<CODE>idCmd<\/CODE> parameter, with interpretive assistance\nin the <CODE>GCS_UNICODE<\/CODE> flag of the <CODE>uType<\/CODE>\nparameter.\n<\/P>\n<P>\nIf the command is not valid, then we propagate the error code,\nexcept in the <CODE>GCS_VALIDATE<\/CODE> cases, where the documentation\nsays that we should return <CODE>S_FALSE<\/CODE> to indicate that\nthe command is not valid.\n<\/P>\n<P>\nIf the command is valid, we return the requested\ninformation, which is handled by a simple <CODE>switch<\/CODE>\nstatement.\n<\/P>\n<P>\nOkay, now that we have this context menu, we can even test it\nout a little bit.  Throw out the changes from\n<A HREF=\"\/oldnewthing\/archive\/2004\/10\/04\/237507.aspx\">\npart 9<\/A>\nand return to the program as it was in\n<A HREF=\"\/oldnewthing\/archive\/2004\/09\/28\/235242.aspx\">\npart 6<\/A>, making the following change to the\n<CODE>OnContextMenu<\/CODE> function:\n<\/P>\n<PRE>\nvoid OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)\n{\n  POINT pt = { xPos, yPos };\n  if (pt.x == -1 &amp;&amp; pt.y == -1) {\n    pt.x = pt.y = 0;\n    ClientToScreen(hwnd, &amp;pt);\n  }<\/p>\n<p>  IContextMenu *pcm;\n  if (SUCCEEDED(<FONT COLOR=\"blue\">CTopContextMenu::Create(<\/FONT>\n                    IID_IContextMenu, (void**)&amp;pcm))) {\n    &#8230;\n<\/PRE>\n<P>\nWe now obtain our context menu not by calling\nthe <CODE>GetUIObjectOfFile<\/CODE> function but rather\nby constructing a <CODE>CTopContextMenu<\/CODE> object.\nSince our <CODE>CTopContextMenu<\/CODE> implements\n<CODE>IContextMenu<\/CODE>, all the remaining code can be\nleft unchanged.\n<\/P>\n<P>\nWhen you run this program, observe that even the help text works.\n<\/P>\n<P>\nAh, one of the powers of operating with interfaces rather than\nobjects:  You can swap out the object and the rest of the code\ndoesn&#8217;t even realize what happened, so long as the interface stays\nthe same.\n<\/P>\n<P>\nOkay, today was a long day spent just laying groundwork,\njust writing what has to be written.  No breakthroughs,\nno &#8220;aha&#8221; moments, just typing.  Read the method, understand\nwhat you have to do, and do it.\n<\/P>\n<P>\n<A HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/10\/07\/239197.aspx\">\nNext time<\/A>, we&#8217;re going to see context menu composition,\nusing this context menu as one of the components.\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Preparing to combine multiple context menu extensions into one.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-37643","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Preparing to combine multiple context menu extensions into one.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/37643","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=37643"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/37643\/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=37643"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=37643"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=37643"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}