{"id":14623,"date":"2010-03-12T07:00:01","date_gmt":"2010-03-12T07:00:01","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2010\/03\/12\/simplifying-context-menu-extensions-with-iexecutecommand\/"},"modified":"2010-03-12T07:00:01","modified_gmt":"2010-03-12T07:00:01","slug":"simplifying-context-menu-extensions-with-iexecutecommand","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20100312-01\/?p=14623","title":{"rendered":"Simplifying context menu extensions with IExecuteCommand"},"content":{"rendered":"<p>\nThe <code>IExecuteCommand<\/code> interface is a simpler form of\ncontext menu extension which takes care of the annoying parts of\n<code>IContextMenu<\/code> so you can focus on your area of expertise,\nnamely, doing the actual thing the user selected,\nand leave the shell to doing the grunt work of managing the UI part.\n<\/p>\n<p>\nI&#8217;ve never needed a scratch shell extension before, so I guess\nit&#8217;s time to create one.\nThis part is completely boring, and those of you who have written\nCOM inproc servers can skip over it.\n<\/p>\n<pre>\n#include &lt;windows.h&gt;\n#include &lt;new&gt;\nLONG g_cObjs;\nvoid DllAddRef() { InterlockedIncrement(&amp;g_cObjs); }\nvoid DllRelease() { InterlockedDecrement(&amp;g_cObjs); }\n\/\/ guts of shell extension go in here eventually\nclass CFactory : public IClassFactory\n{\npublic:\n \/\/ *** IUnknown ***\n STDMETHODIMP QueryInterface(REFIID riid, void **ppv);\n STDMETHODIMP_(ULONG) AddRef() { return 2; }\n STDMETHODIMP_(ULONG) Release() { return 1; }\n \/\/ *** IClassFactory ***\n STDMETHODIMP CreateInstance(IUnknown *punkOuter,\n                             REFIID riid, void **ppv);\n STDMETHODIMP LockServer(BOOL fLock);\n};\nCFactory c_Factory;\nSTDMETHODIMP CFactory::QueryInterface(REFIID riid, void **ppv)\n{\n IUnknown *punk = NULL;\n if (riid == IID_IUnknown || riid == IID_IClassFactory) {\n  punk = static_cast&lt;IClassFactory*&gt;(this);\n }\n *ppv = punk;\n if (punk) {\n  punk-&gt;AddRef();\n  return S_OK;\n } else {\n  return E_NOINTERFACE;\n }\n}\nSTDMETHODIMP CFactory::CreateInstance(\n IUnknown *punkOuter, REFIID riid, void **ppv)\n{\n *ppv = NULL;\n if (punkOuter) return CLASS_E_NOAGGREGATION;\n CShellExtension *pse = new(std::nothrow) CShellExtension();\n if (!pse) return E_OUTOFMEMORY;\n HRESULT hr = pse-&gt;QueryInterface(riid, ppv);\n pse-&gt;Release();\n return hr;\n}\nSTDMETHODIMP CFactory::LockServer(BOOL fLock)\n{\n if (fLock) DllAddRef();\n else       DllRelease();\n return S_OK;\n}\nSTDAPI DllGetClassObject(REFCLSID rclsid,\n                         REFIID riid, void **ppv)\n{\n if (rclsid == CLSID_ShellExtension) {\n  return c_Factory.QueryInterface(riid, ppv);\n }\n *ppv = NULL;\n return CLASS_E_CLASSNOTAVAILABLE;\n}\nSTDAPI DllCanUnloadNow()\n{\n return g_cObjs ? S_OK : S_FALSE;\n}\n<\/pre>\n<p>\nI&#8217;m assuming that the above code is all old hat.\nConsider it a prerequisite.\n<\/p>\n<p>\nOkay, now the good stuff.\n<\/p>\n<p>\nThe <code>IExecuteCommand<\/code> interface is used when you\ncreate a static registration for a shell verb but\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb762475.aspx\">\nspecify <code>DelegateExecute<\/code> in the command<\/a>.\nOur sample shell extension will be active on text files,\nand all it&#8217;ll do is print the file names to the debugger.\n<\/p>\n<p>\nSince we&#8217;re a COM server, we need to register our CLSID.\nThis should also be very familiar to you.\n<\/p>\n<pre>\n[HKEY_CLASSES_ROOT\\CLSID\\{<font COLOR=\"blue\">guid<\/font>}\\InProcServer32]\n@=\"<font COLOR=\"blue\">C:\\path\\to\\scratch.dll<\/font>\"\n\"ThreadingModel\"=\"Apartment\"\n<\/pre>\n<p>\nHere&#8217;s where we register our object as a verb for text files,\nspecifying that it should be invoked via <code>DelegateExecute<\/code>:\n<\/p>\n<pre>\n[HKEY_CLASSES_ROOT\\txtfile\\shell\\printnamestodebugger]\n@=\"Print names to debugger\"\n[HKEY_CLASSES_ROOT\\txtfile\\shell\\printnamestodebugger\\command]\n\"DelegateExecute\"=\"{<font COLOR=\"blue\">guid<\/font>}\"\n<\/pre>\n<p>\nThat was the easy part. Now to roll up our sleeves and write\nthe shell extension.\n<\/p>\n<pre>\n#include &lt;shobjidl.h&gt;\nCLSID CLSID_ShellExtension = { <font COLOR=\"blue\">...guid...<\/font> };\nclass CShellExtension\n : public IExecuteCommand\n , public IInitializeCommand\n , public IObjectWithSelection\n{\npublic:\n CShellExtension();\n \/\/ *** IUnknown ***\n STDMETHODIMP QueryInterface(REFIID riid, void **ppv);\n STDMETHODIMP_(ULONG) AddRef();\n STDMETHODIMP_(ULONG) Release();\n \/\/ *** IInitializeCommand ***\n STDMETHODIMP Initialize(PCWSTR pszCommandName, IPropertyBag *ppb);\n \/\/ *** IObjectWithSelection ***\n STDMETHODIMP SetSelection(IShellItemArray *psia);\n STDMETHODIMP GetSelection(REFIID riid, void **ppv);\n \/\/ *** IExecuteCommand ***\n STDMETHODIMP SetKeyState(DWORD grfKeyState) { return S_OK; }\n STDMETHODIMP SetParameters(LPCWSTR pszParameters) { return S_OK; }\n STDMETHODIMP SetPosition(POINT pt) { return S_OK; }\n STDMETHODIMP SetShowWindow(int nShow) { return S_OK; }\n STDMETHODIMP SetNoShowUI(BOOL fNoShowUI) { return S_OK; }\n STDMETHODIMP SetDirectory(LPCWSTR pszDirectory) { return S_OK; }\n STDMETHODIMP Execute();\nprivate:\n ~CShellExtension();\nprivate:\n LONG m_cRef;\n IShellItemArray *m_psia;\n};\nCShellExtension::CShellExtension()\n : m_cRef(1), m_psia(NULL)\n{\n DllAddRef();\n}\nCShellExtension::~CShellExtension()\n{\n if (m_psia) m_psia-&gt;Release();\n DllRelease();\n}\n<\/pre>\n<p>\nI&#8217;ve written this all out longhand; I&#8217;m trusting that you&#8217;re\nusing some sort of framework (like, say, ATL) which avoids all\nthis tedium, but since different people may choose different frameworks,\nI won&#8217;t choose a framework here.\nInstead, we just have the boring <code>IUnknown<\/code> methods.\n<\/p>\n<pre>\nSTDMETHODIMP CShellExtension::QueryInterface(\n REFIID riid, void **ppv)\n{\n IUnknown *punk = NULL;\n if (riid == IID_IUnknown || riid == IID_IExecuteCommand) {\n  punk = static_cast&lt;IExecuteCommand*&gt;(this);\n } else if (riid == IID_IInitializeCommand) {\n  punk = static_cast&lt;IInitializeCommand*&gt;(this);\n } else if (riid == IID_IObjectWithSelection) {\n  punk = static_cast&lt;IObjectWithSelection*&gt;(this);\n }\n *ppv = punk;\n if (punk) {\n  punk-&gt;AddRef();\n  return S_OK;\n } else {\n  return E_NOINTERFACE;\n }\n}\nSTDMETHODIMP_(ULONG) CShellExtension::AddRef()\n{\n return ++m_cRef;\n}\nSTDMETHODIMP_(ULONG) CShellExtension::Release()\n{\n ULONG cRef = --m_cRef;\n if (cRef == 0) delete this;\n return cRef;\n}\n<\/pre>\n<p>\nWhew.\nUp until now, it&#8217;s just been boring typing that you have to do\nfor any shell extension.\nFinally we can start doing something interesting.\nWindows&nbsp;7 will initialize your shell extension with information\nabout the command being executed.\nFor this particular shell extension, we&#8217;ll just print the command\nname to the debugger to prove that something happened.\n(In real life, you might use the same <code>CShellExtension<\/code>\nto handle multiple commands, and this lets you determine which\ncommand you&#8217;re being asked to execute.)\n<\/p>\n<pre>\nSTDMETHODIMP CShellExtension::Initialize(\n PCWSTR pszCommandName,\n IPropertyBag *ppb)\n{\n OutputDebugStringW(L\"Command: \");\n OutputDebugStringW(pszCommandName);\n OutputDebugStringW(L\"\\r\\n\");\n return S_OK;\n}\n<\/pre>\n<p>\nThe shell will give you the items on which to execute in the form\nof an <code>IShellItemArray<\/code>:\n<\/p>\n<pre>\nSTDMETHODIMP CShellExtension::SetSelection(IShellItemArray *psia)\n{\n if (psia) <a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/04\/06\/108395.aspx\">psia-&gt;AddRef()<\/a>;\n if (m_psia) m_psia-&gt;Release();\n m_psia = psia;\n return S_OK;\n}\nSTDMETHODIMP CShellExtension::GetSelection(\n REFIID riid, void **ppv)\n{\n if (m_psia) return m_psia-&gt;QueryInterface(riid, ppv);\n *ppv = NULL;\n return E_NOINTERFACE;\n}\n<\/pre>\n<p>\nThe shell will then call a bunch of <code>IExecuteCommand::SetThis<\/code>\nand\n<code>IExecuteCommand::SetThat<\/code> methods to inform you of the\nenvironment in which you have been asked to execute.\nWe just ignored them all for simplicity, but in practice,\nyou may want to pay attention to some of them, particularly\n<code>IExecuteCommand::SetPosition<\/code>,\n<code>IExecuteCommand::SetShowWindow<\/code>, and\n<code>IExecuteCommand::SetNoShowUI<\/code>.\n<\/p>\n<p>\nAfter all the <code>IExecuteCommand::SetXxx<\/code> methods have been called,\nit&#8217;s show time:\n<\/p>\n<pre>\nSTDMETHODIMP CShellExtension::Execute()\n{\n HRESULT hr;\n if (m_psia) {\n  IEnumShellItems *pesi;\n  if (SUCCEEDED(hr = m_psia-&gt;EnumItems(&amp;pesi))) {\n   IShellItem *psi;\n   while (pesi-&gt;Next(1, &amp;psi, NULL) == S_OK) {\n    LPWSTR pszName;\n    if (SUCCEEDED(psi-&gt;GetDisplayName(SIGDN_FILESYSPATH,\n                                      &amp;pszName))) {\n     OutputDebugStringW(L\"File: \");\n     OutputDebugStringW(pszName);\n     OutputDebugStringW(L\"\\r\\n\");\n     CoTaskMemFree(pszName);\n    }\n    psi-&gt;Release();\n   }\n   pesi-&gt;Release();\n   hr = S_OK;\n  }\n } else {\n  hr = E_UNEXPECTED;\n }\n return hr;\n}\n<\/pre>\n<p>\nAll we do is enumerate the contents of the <code>IShellItemArray<\/code>\nand print their file names (if they have one).\nInstead of <code>IEnumShellItems<\/code>, you can use\n<code>IShellItemArray::GetCount<\/code> and\n<code>IShellItemArray::GetItemAt<\/code>.\nOr, if you are porting an existing context menu that uses\n<code>IDataObject<\/code>,\nyou can call <code>IShellItemArray::BindToHandler(BHID_DataObject)<\/code>\nto turn your <code>IShellItemArray<\/code> into an <code>IDataObject<\/code>.\n<\/p>\n<p>\nInstall this shell extension, right-click on a text file (or\na bunch of text files), and select\n<i>Print names to debugger<\/i>.\nIf all goes well, the debugger will report\n<code>Command: printnamestodebugger<\/code>\nfollowed by paths of the files you selected.\n<\/p>\n<p>\nBut wait, there&#8217;s more.\nThe <code>IPropertyBag<\/code> passed to\n<code>IInitializeCommand::Initialize<\/code>\ncontains additional configuration options taken from the\nregistry.\nYou can use this to customize the behavior of the shell extension further.\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378347.aspx\">\nPut the bonus information under the command key<\/a>\nlike this:\n<\/p>\n<pre>\n[HKEY_CLASSES_ROOT\\txtfile\\shell\\printnamestodebugger]\n\"extra\"=\"Special\"\n<\/pre>\n<pre>\nSTDMETHODIMP CShellExtension::Initialize(\n PCWSTR pszCommandName,\n IPropertyBag *ppb)\n{\n OutputDebugStringW(L\"Command: \");\n OutputDebugStringW(pszCommandName);\n OutputDebugStringW(L\"\\r\\n\");\n <font COLOR=\"blue\">if (ppb) {\n  VARIANT vt;\n  VariantInit(&amp;vt);\n  if (SUCCEEDED(ppb-&gt;Read(L\"extra\", &amp;vt, NULL))) {\n   if (SUCCEEDED(VariantChangeType(&amp;vt, &amp;vt, 0, VT_BSTR))) {\n    OutputDebugStringW(L\"extra: \");\n    OutputDebugStringW(vt.bstrVal);\n    OutputDebugStringW(L\"\\r\\n\");\n   }\n   VariantClear(&amp;vt);\n  }\n }<\/font>\n return S_OK;\n}\n<\/pre>\n<p>\nThis updated version of <code>CShellExtension<\/code> looks for\nthat registry value <code>extra<\/code> we set above\nand if found prints its value to the debugger.\n<\/p>\n<p>\nOkay, so it looks like a lot of typing, but most of that was\ntyping you have to do for any shell extension.\nThe part that is specific to <code>IExecuteCommand<\/code> is not\nthat bad,\nand it certainly avoids having to mess with\n<code>IContextMenu::QueryContextMenu<\/code>\nand the fifty bajillion variations on\n<code>IContextMenu::InvokeCommand<\/code>.\nFurthermore,\nthe shell doesn&#8217;t even load your <code>IExecuteCommand<\/code> handler\nuntil the user selects your command,\nso switching to a static registration also gives the system a bit of a\nperformance boost.\n<\/p>\n<p>\n<b>Bonus tip<\/b>:\nYou can combine the <code>IExecuteCommand<\/code> technique with\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc144171.aspx#dynamic_behavior\">\n<i>Getting Dynamic Behavior for Static Verbs by Using Advanced Query Syntax<\/i>\n<\/a>\nand\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc144171.aspx#attributes_items\">\n<i>Using Item Attributes<\/i><\/a>\nto specify the conditions under which you want your verb to appear\nwithout having to write a single line of C++ code.\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd758091.aspx\">\nChoosing a Static or Dynamic Shortcut Menu Method<\/a>\nprovides additional guidance on choosing among the various methods\nfor registering verbs.\n<\/p>\n<p>\nOne nice thing about <code>IExecuteCommand<\/code> is that it supports\nout-of-proc activation (i.e., local server rather than in-proc server).\nThis means that it supports cross-bitness shell extensions:\nIf you don&#8217;t have the time to port your 32-bit shell extension to 64-bit,\nyou can register it as an out-of-proc <code>IExecuteCommand<\/code>.\nWhen running on 64-bit Windows,\nthe 64-bit Explorer will launch your 32-bit server to handle the command.\nConversely, if your <code>IExecuteCommand<\/code> is a 64-bit local server,\na 32-bit application can still invoke it.\n<\/p>\n<p>\n(We&#8217;ll see more about local server shell extensions in a few months.\nThis was just foreshadowing.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The IExecuteCommand interface is a simpler form of context menu extension which takes care of the annoying parts of IContextMenu so you can focus on your area of expertise, namely, doing the actual thing the user selected, and leave the shell to doing the grunt work of managing the UI part. I&#8217;ve never needed a [&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-14623","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The IExecuteCommand interface is a simpler form of context menu extension which takes care of the annoying parts of IContextMenu so you can focus on your area of expertise, namely, doing the actual thing the user selected, and leave the shell to doing the grunt work of managing the UI part. I&#8217;ve never needed a [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/14623","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=14623"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/14623\/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=14623"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=14623"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=14623"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}