{"id":14183,"date":"2010-05-03T07:00:00","date_gmt":"2010-05-03T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2010\/05\/03\/how-do-i-accept-files-to-be-opened-via-idroptarget-instead-of-on-the-command-line\/"},"modified":"2010-05-03T07:00:00","modified_gmt":"2010-05-03T07:00:00","slug":"how-do-i-accept-files-to-be-opened-via-idroptarget-instead-of-on-the-command-line","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20100503-00\/?p=14183","title":{"rendered":"How do I accept files to be opened via IDropTarget instead of on the command line?"},"content":{"rendered":"<p>\nCommenter Yaron wants to know\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/pages\/407234.aspx#1353948\">\nhow to use the new IDropTarget mechanism for receiving a list of files\nto open<\/a>.\n(Also asked by\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2008\/03\/20\/8080229.aspx#8337835\">Anthony Wieser<\/a> as a comment to an article.)\nThe MSDN documentation on Verbs and File Assocations mentions that\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2007\/02\/26\/1763683.aspx\">\nDDE has been deprecated as a way of launching documents<\/a>\nand that\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc144175.aspx\">\nyou should use the DropTarget method instead<\/a>.\nBut what is the DropTarget method?\n(Note that the word <i>method<\/i> here is in the sense of\n<i>technique<\/i> and not in the C++ sense of <i>function that\nbelongs to a class<\/i>.)\n<\/p>\n<p>\nThe documentation in MSDN tells you what to do, but it does so\nvery tersely. It says to create a <code>DropTarget<\/code>\nkey under the verb key and create a <code>Clsid<\/code> string\nvalue whose data is the string version of the CLSID for your\ndrop target.\nThe documentation tells you to be careful in your\n<code>IDropTarget::Drop<\/code>, so it stands to reason that\n<code>IDropTarget<\/code> is the interface that will be used.\nFrom context, therefore, you should expect that the shell is going to\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2008\/07\/24\/8768095.aspx\">\nsimulate a drop<\/a> on your drop target.\n<\/p>\n<p>\nYou can implement your drop target either as an in-process server\nor a local server.\nThe in-process case is well-known;\nnearly all shell extensions are in-process.\nBut using an in-process server for the DropTarget technique\nonly solves half the problem:\nSure, the <code>IDropTarget::Drop<\/code> will take place and you will\nget your <code>IDataObject<\/code>,\nbut you still have to transfer the file list from your shell extension\nrunning inside the\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/09\/20\/231739.aspx\">\ncontext menu host<\/a>\nto your application.\nMay as well let COM do the heavy lifting of marshalling the data.\n(Well, okay, maybe using COM is overkill.\nYou might have a lighter weight way of getting the data across,\nbut since that&#8217;s out of scope for today&#8217;s exercise, I&#8217;ll leave it\nfor you to figure out.)\n<\/p>\n<p>\nOkay, let&#8217;s roll up our sleeves and get to it!\nIt turns out that nearly all the work is just creating a COM local\nserver.\nIf you know how to do that already, then I apologize in advance\nfor the oppressive boredom about to fall upon you.\nI&#8217;ll try to remember to wake you up when something interesting\nis about to happen.\nNote also that I am not an expert on COM local servers,\nso if you find a discrepancy between what I write and information from people\nwho actually know what they&#8217;re doing, go with the people who\nknow what they&#8217;re doing.\n(Actually, that sentence pretty much applies in general to everything\nI write.)\nIndeed, I had never written a COM local server before now,\nso all of what you see here is the result of a crash course\nin COM local servers from reading the documentation.\n(Translation: You could&#8217;ve done this too.)\n<\/p>\n<p>\nStart by adding some header files and a forward reference.\n<\/p>\n<pre>\n#include &lt;shlobj.h&gt;\n#include &lt;shellapi.h&gt;\n#include &lt;new&gt; \/\/ for new(nothrow)\nvoid OpenFilesFromDataObject(IDataObject *pdto);\n<\/pre>\n<p>\nNext, I&#8217;m going to steal\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2008\/05\/28\/8555658.aspx\">\nthe <code>Process&shy;Reference<\/code> class<\/a>\nwhich I had created some time ago.\nIt&#8217;s not the most efficient solution to the problem,\nbut it works well enough, and it&#8217;s a nice preparatory step in case\na shell extension loaded into our process needs to take a process\nreference.\nWe use the process reference object to keep track of our outstanding\nobjects and locks.\n<\/p>\n<pre>\nProcessReference *g_ppr;\n<\/pre>\n<p>\nOf course our custom drop target needs a class ID:\n<\/p>\n<pre>\nconst CLSID CLSID_Scratch = { ... };\n<\/pre>\n<p>\nI leave it to you to fill in the <code>CLSID<\/code> structure\nfrom the output of <code>uuidgen -s<\/code>.\n<\/p>\n<p>\nNext, our simple drop target.\nCOM servers need to keep track of the number of objects that\nhave been created, so we&#8217;ll piggyback off our existing process reference.\n<\/p>\n<pre>\nclass SimpleDropTarget : public IDropTarget\n{\npublic:\n SimpleDropTarget() : m_cRef(1) { g_ppr-&gt;AddRef(); }\n ~SimpleDropTarget() { g_ppr-&gt;Release(); }\n \/\/ *** IUnknown ***\n STDMETHODIMP QueryInterface(REFIID riid, void **ppv)\n {\n  if (riid == IID_IUnknown || riid == IID_IDropTarget) {\n    *ppv = static_cast&lt;IUnknown*&gt;(this);\n    AddRef();\n    return S_OK;\n  }\n  *ppv = NULL;\n  return E_NOINTERFACE;\n }\n STDMETHODIMP_(ULONG) AddRef()\n {\n  return InterlockedIncrement(&amp;m_cRef);\n }\n STDMETHODIMP_(ULONG) Release()\n {\n  LONG cRef = InterlockedDecrement(&amp;m_cRef);\n  if (cRef == 0) delete this;\n  return cRef;\n }\n<\/pre>\n<p>\nNext come the methods of <code>IDropTarget<\/code>,\nnone of which are particularly interesting.\nWe just say that we are going to copy the data.\n<\/p>\n<pre>\n \/\/ *** IDropTarget ***\n STDMETHODIMP DragEnter(IDataObject *pdto,\n    DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)\n {\n  *pdwEffect &amp;= DROPEFFECT_COPY;\n  return S_OK;\n }\n STDMETHODIMP DragOver(DWORD grfKeyState,\n   POINTL ptl, DWORD *pdwEffect)\n {\n  *pdwEffect &amp;= DROPEFFECT_COPY;\n  return S_OK;\n }\n STDMETHODIMP DragLeave()\n {\n  return S_OK;\n }\n STDMETHODIMP Drop(IDataObject *pdto, DWORD grfKeyState,\n    POINTL ptl, DWORD *pdwEffect)\n {\n  <font COLOR=\"blue\">OpenFilesFromDataObject(pdto);<\/font>\n  *pdwEffect &amp;= DROPEFFECT_COPY;\n  return S_OK;\n }\nprivate:\n LONG m_cRef;\n};\n<\/pre>\n<p>\n<b>People who know how COM servers work wake up<\/b>:\nWhen something is dropped on our drop target, we call\n<code>Open&shy;Files&shy;From&shy;Data&shy;Object<\/code>.\nThat&#8217;s actually not all that interesting, but at least it&#8217;s\nnontrivial.\n<b>People who know how COM servers work can go back to sleep now<\/b>.\n<\/p>\n<p>\nThe next part of the code is just setting up our class factory.\n<\/p>\n<pre>\nclass SimpleClassFactory : public IClassFactory\n{\npublic:\n \/\/ *** IUnknown ***\n STDMETHODIMP QueryInterface(REFIID riid, void **ppv)\n {\n  if (riid == IID_IUnknown || riid == IID_IClassFactory) {\n    *ppv = static_cast&lt;IUnknown*&gt;(this);\n    AddRef();\n    return S_OK;\n  }\n  *ppv = NULL;\n  return E_NOINTERFACE;\n }\n STDMETHODIMP_(ULONG) AddRef()\n {\n  return 2;\n }\n STDMETHODIMP_(ULONG) Release()\n {\n  return 1;\n }\n \/\/ *** IClassFactory ***\n STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)\n {\n    *ppv = NULL;\n    if (punkOuter) return CLASS_E_NOAGGREGATION;\n    SimpleDropTarget *pdt = new(nothrow) SimpleDropTarget();\n    if (!pdt) return E_OUTOFMEMORY;\n    HRESULT hr = pdt-&gt;QueryInterface(riid, ppv);\n    pdt-&gt;Release();\n    return hr;\n }\n STDMETHODIMP LockServer(BOOL fLock)\n {\n  if (!g_ppr) return E_FAIL; \/\/ server shutting down\n  if (fLock) g_ppr-&gt;AddRef(); else g_ppr-&gt;Release();\n  return S_OK;\n }\n};\nSimpleClassFactory s_scf;\n<\/pre>\n<p>\nThe <code>Lock&shy;Server<\/code> call takes advantage of our\nprocess reference object by forwarding lock and unlock\ncalls into the reference count of the process reference object.\nThis keeps our process running until the server is unlocked.\n<\/p>\n<p>\nRemember that COM rules specify that the class factory itself\ndoes not count as an outstanding COM object, so we don&#8217;t use\nthe same <code>m_punkProcess<\/code> trick that we did with\nour drop target.\nInstead, we just use a static object.\n<\/p>\n<p>\n<b>People who know how COM servers work wake up<\/b>:\nThe COM server code is pretty much done.\nNow we&#8217;re back to user interface programming.\n<\/p>\n<p>\nThe next part of the code is just copied from our scratch program,\nwith the following changes:\n<\/p>\n<pre>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n    <font COLOR=\"blue\">g_hwndChild = CreateWindow(\n        TEXT(\"listbox\"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP,\n        0, 0, 0,0, hwnd, (HMENU)1, g_hinst, 0);<\/font>\n    return TRUE;\n}\n<\/pre>\n<p>\nThe list box is not an important part of the program.\nWe&#8217;ll just fill it with data to prove that we actually did something.\n<\/p>\n<pre>\nvoid OpenFilesFromDataObject(IDataObject *pdto)\n{\n if (!g_hwndChild) { \/* need to create a new main window *\/ }\n FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT,\n                    -1, TYMED_HGLOBAL };\n STGMEDIUM stgm;\n if (SUCCEEDED(pdto-&gt;GetData(&amp;fmte, &amp;stgm))) {\n  HDROP hdrop = reinterpret_cast&lt;HDROP&gt;(stgm.hGlobal);\n  UINT cFiles = DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);\n  for (UINT i = 0; i &lt; cFiles; i++) {\n   TCHAR szFile[MAX_PATH];\n   UINT cch = DragQueryFile(hdrop, i, szFile, MAX_PATH);\n   if (cch &gt; 0 &amp;&amp; cch &lt; MAX_PATH) {\n    ListBox_AddString(g_hwndChild, szFile);\n   }\n  }\n  ReleaseStgMedium(&amp;stgm);\n }\n}\n<\/pre>\n<p>\nThe <code>Open&shy;Files&shy;From&shy;Data&shy;Object<\/code> function does only\nenough work to prove that it actually got the list of file names.\nWhen we receive a data object from the simulated drop,\nwe retrieve the <code>HDROP<\/code> and enumerate the files in it.\nFor each file, we add it to the list box.\n<\/p>\n<p>\nThere&#8217;s some code I&#8217;ve not bothered to write: Namely, if\na request to open some files comes in after the user closed\nour main window, we need to open a new main window.\n(Exercise: How can this happen?)\n<\/p>\n<p>\nAnother difference between this program and real life is that in\nreal life, your <code>Open&shy;Files&shy;From&shy;Data&shy;Object<\/code>\nwould do some real work.\n<i>But wait<\/i>,\nif your function does any actual work,\nyou should just <code>AddRef<\/code> the data object and return,\nso that the shell can return to interacting with the user.\nIf you stop to do a lot of work before returning, the shell will\nlock up because it&#8217;s waiting for your drop to complete.\n<\/p>\n<pre>\n\/\/ Version of OpenFilesFromDataObject that is more\n\/\/ appropriate for real life.\nvoid OpenFilesFromDataObject(IDataObject *pdto)\n{\n if (!g_hwndChild) { \/* need to create a new main window *\/ }\n pdto-&gt;AddRef();\n PostMessage(GetParent(g_hwndChild), WM_OPENFILES, 0,\n             reinterpret_cast&lt;LPARAM&gt;(pdto));\n}\ncase WM_OPENFILES:\n IDataObject *pdto = reinterpret_cast&lt;IDataObject*&gt;(lParam);\n ... rest of code from the original OpenFilesFromDataObject ...\n pdto-&gt;Release();\n break;\n<\/pre>\n<p>\nIn real life, you just <code>AddRef<\/code> the data object\nand then post a message to finish processing it later.\nThe aim here is to release the shell thread as quickly as\npossible.\nWhen the posted message is received, we can\nextract the information from the data object at our leisure.\n<b>People who know how COM servers work can go back to sleep now<\/b>.\n<\/p>\n<p>\nFinally, we hook up our class factories to the main program:\n<\/p>\n<pre>\nint WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,\n                   LPSTR lpCmdLine, int nShowCmd)\n{\n    MSG msg;\n    HWND hwnd;\n    g_hinst = hinst;\n    if (!InitApp()) return 0;\n    if (SUCCEEDED(CoInitialize(NULL))) {\/* In case we use COM *\/\n        <font COLOR=\"blue\">HRESULT hrRegister;\n        DWORD dwRegisterCookie;\n        {\n            ProcessReference ref;\n            g_ppr = &amp;ref;\n            hrRegister = CoRegisterClassObject(CLSID_Scratch, &amp;s_scf,\n                  CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,\n                  &amp;dwRegisterCookie);<\/font>\n            hwnd = CreateWindow(\n                TEXT(\"Scratch\"),                \/* Class Name *\/\n                TEXT(\"Scratch\"),                \/* Title *\/\n                WS_OVERLAPPEDWINDOW,            \/* Style *\/\n                CW_USEDEFAULT, CW_USEDEFAULT,   \/* Position *\/\n                CW_USEDEFAULT, CW_USEDEFAULT,   \/* Size *\/\n                NULL,                           \/* Parent *\/\n                NULL,                           \/* No menu *\/\n                hinst,                          \/* Instance *\/\n                0);                             \/* No special parameters *\/\n            <font COLOR=\"blue\">if (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,\n                 lpCmdLine, -1, \"-Embedding\", -1) != CSTR_EQUAL &amp;&amp;\n                CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,\n                 lpCmdLine, -1, \"\/Embedding\", -1) != CSTR_EQUAL) {\n                \/* OpenFilesFromCommandLine(); *\/\n            }<\/font>\n            ShowWindow(hwnd, nShowCmd);\n            while (GetMessage(&amp;msg, NULL, 0, 0)) {\n                TranslateMessage(&amp;msg);\n                DispatchMessage(&amp;msg);\n            }\n            <font COLOR=\"blue\">g_hwndChild = NULL;\n        } \/\/ wait for process references to die\n        g_ppr = NULL;\n        if (SUCCEEDED(hrRegister)) {\n            CoRevokeClassObject(dwRegisterCookie);\n        }<\/font>\n        CoUninitialize();\n    }\n    return 0;\n}\n<\/pre>\n<p>\nAfter creating our process reference,\nwe register our class factory by calling\n<code>Co&shy;Register&shy;Class&shy;Object<\/code>.\nWe do this even if not invoked by COM, because we want COM to\nbe able to find us once we&#8217;re up and running:\nIf the user runs the application manually and then double-clicks\nan associated document, we want that document to be handed to us\nrather than having COM launch another copy of the program.\n<\/p>\n<p>\nAfter creating the window, we check if the command line is\n<code>-Embedding<\/code> or <code>\/Embedding<\/code>.\nThis is the magic command line switch which COM gives us\nwhen we are being launched as a local server.\nIf we don&#8217;t have that switch, then we&#8217;re being launched\nwith a file name on our command line, so proceed with\n&#8220;old school&#8221; command line parsing.\n(I didn&#8217;t bother writing the\n<code>Open&shy;Files&shy;From&shy;Command&shy;Line<\/code>\nfunction since it is irrelevant to the topic.)\n<\/p>\n<p>\nAfter our message loop exits, we clear the <code>g_hwndChild<\/code>\nso <code>Open&shy;Files&shy;From&shy;Data&shy;Object<\/code>\nknows that there is no\nmain window any more.\nIn real life, we&#8217;d have to create a new main window and restart\nthe message loop.\n<\/p>\n<p>\nOnce all outstanding COM objects and server locks and process\nreferences are gone,\nwe can tear down the process.\nWe unregister the COM server (if we registered it)\nso that COM won&#8217;t try to ask us to open any more documents.\n(COM will instead launch a new copy of the program.)\n<\/p>\n<p>\nAnd that&#8217;s it.\n<\/p>\n<p>\nOh wait, we also have to register this program so COM and the shell\ncan find us.\n<\/p>\n<p>\nRegistering the COM server is just a matter of setting the registry\nkey\n<\/p>\n<pre>\n[HKCR\\CLSID\\{...}\\LocalServer32]\n@=\"C:\\\\Path\\\\To\\\\scratch.exe\"\n<\/pre>\n<p>\nYou probably should also set a friendly name into\n<code>HKCR\\CLSID\\{...}<\/code> so people will have\na clue what your server is for.<\/p>\n<p>\n<b>People who know how COM servers work wake up<\/b>:\nAfter we register our class with COM,\nwe can register it with the shell.\nFor demonstration purposes, we&#8217;ll make our command a secondary\nverb on text files.\n<\/p>\n<pre>\n[HKCR\\txtfile\\shell\\scratch\\DropTarget]\n\"Clsid\"=\"{...}\"\n<\/pre>\n<p>\nWow, all we had to do was set two registry values and boom,\nwe can now accept files via drop target.\nMultiselect a whole bunch of text files,\nright-click them,\nand then select &#8220;scratch&#8221;.\nThe shell sees that the verb is registered as a drop target,\nso it calls <code>Co&shy;Create&shy;Instance<\/code> on the <code>CLSID<\/code>\nyou provided.\nCOM looks up the <code>CLSID<\/code> in the registry and finds the\npath to your program.\nYour program runs with the <code>-Embedding<\/code> flag,\nregisters its class factory, and awaits further instructions.\nCOM asks your class factory to create a drop target,\nwhich it returns to the shell.\nThe shell then performs the simulated drop,\nand when you get the <code>IDropTarget::Drop<\/code>,\nyour code springs into action and extracts all the files in the\ndata object.\n<\/p>\n<p>\nNow that we have all this working, it&#8217;s just one more tiny\nstep to register your application&#8217;s drop target so that\nit is invoked\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc144150.aspx\">\nwhen the user drops a group of files on the\nEXE itself<\/a>\n(or on a shortcut to the EXE):\n<\/p>\n<pre>\n[HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\scratch.exe]\n\"DropTarget\"=\"{...}\"\n<\/pre>\n<p>\nWith this additional registration, grab that bunch of files\nand drop it on the <code>scratch.exe<\/code> icon.\nInstead of passing all those files on the command line\n(and possibly overflowing the command line limit),\nthe shell goes through the same procedure as it did with the\ncontext menu to hand the list of files to your program\nvia the data object.\n<\/p>\n<p>\nNearly all of the work here was just managing the COM local server.\nThe parts that had to do with the shell were actually quite small.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Commenter Yaron wants to know how to use the new IDropTarget mechanism for receiving a list of files to open. (Also asked by Anthony Wieser as a comment to an article.) The MSDN documentation on Verbs and File Assocations mentions that DDE has been deprecated as a way of launching documents and that you should [&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-14183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Commenter Yaron wants to know how to use the new IDropTarget mechanism for receiving a list of files to open. (Also asked by Anthony Wieser as a comment to an article.) The MSDN documentation on Verbs and File Assocations mentions that DDE has been deprecated as a way of launching documents and that you should [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/14183","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=14183"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/14183\/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=14183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=14183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=14183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}