1

So I've desperately been trying to automate drag and drop functionality, and have narrowed my search for solutions down to a fairly refined chunk of code:

// DragAndDrop.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>
#include <Shlobj.h>
#include <tchar.h>


int main(int argc, char* argv[]) {

    for (int i = 0; i <= WM_DROPFILES; i++)
    {
        ChangeWindowMessageFilter (i, MSGFLT_ADD);
    }

    if (HWND hwnd = FindWindow ("OpusApp", NULL)) {

    //HGLOBAL hGlobal = GlobalAlloc (GMEM_FIXED,
    //sizeof ("d:\\DragMe.txt") + 2);
    //char *strFile = (char*) GlobalLock
    //(hGlobal);
    //strcpy (strFile, "d:\\DragMe.txt");
    //strFile [strlen ("d:\\DragMe.txt") +
    //1] = NULL;
    char filename[] = "d:\\DragMe.txt";

    POINT point;
    point.x = 480;
    point.y = 480;

    HGLOBAL hMem = GlobalAlloc(GHND, sizeof(DROPFILES) + strlen(filename)+2);

    DROPFILES *dfiles = (DROPFILES*) GlobalLock(hMem);
    if (!dfiles)
    {
        GlobalFree(hMem);
        return NULL;
    }

    dfiles->pFiles = sizeof(DROPFILES);
    dfiles->pt = point;
    dfiles->fNC = TRUE;
    dfiles->fWide = FALSE;
    memcpy(&dfiles[1], filename, strlen(filename));
    GlobalUnlock(hMem);

    printf ("Sending Message...\n");

    if (!PostMessage(hwnd, WM_DROPFILES, (WPARAM)hMem, 0)) {
        printf("Error Posting Message!");
        GlobalFree(hMem);
    }
    }

    int temp = 0;
    scanf("&d", temp);
    return 0;
}

... I apologize for any bad words in my code... they are just for debugging purposes. Anyway, the above is very simple, and it works with Microsoft Word, Excel, and Notepad... but for a number of applications it does not work at all (Spy++ does not even log a WM_DROPFILES message system-wide in these cases, which is strange...). I have even tried compiling the code as x64 or x86 for the problem applications, but no change...

I feel like I may be using FindWindow incorrectly (I'm using the Window Info Tool bundled with AutoIT to get the window class, as I find Spy++ pretty confusing). In anycase, I am setting a bounty because I realllllllly need to get this figured out.

The application I will need to use this with is named Dartfish, and it is a 32-bit app on Windows 7... I need to send a list of video files to a specific region of its interface (specific pane), and I am trying to do this with the above code.

Any help? I greatly appreciate it!!

cf-
  • 8,598
  • 9
  • 36
  • 58
araisbec
  • 1,223
  • 3
  • 16
  • 27
  • Does your target window have WS_EX_ACCEPTFILES style? – Denis Anisimov Mar 08 '14 at 19:00
  • See, that's the problem... it's a third party, closed-source application. I have no idea. So it is entirely possible that I may be barking up the wrong tree, and the application may not even have an event handler for WM_DROPFILES? Or do all windows applications handle this message by default? – araisbec Mar 09 '14 at 13:52
  • To detect WS_EX_ACCEPTFILES style you can use GetWindowLong with GWL_EXSTYLE param. – Denis Anisimov Mar 09 '14 at 15:09
  • `WS_EX_ACCEPTFILES` is just a shortcut to avoid having to call `DragAcceptFiles()`. But no, all apps do not handle `WM_DROPFILES` by default, `DefWindowProc()` discards it. Individual apps have to handle `WM_DROPFILES` to decide how they want to use the dragged filenames. – Remy Lebeau Mar 09 '14 at 21:45
  • Alright, thanks for clearing that up. See below; I have been working on your solution. – araisbec Mar 16 '14 at 14:46

2 Answers2

1

ChangeWindowMessageFilter/Ex() does not grant you the right to send the specified message to other processes. It grants other processes (specifically, lower integrity processes) the right to send that message to you instead. So get rid of it, it is not benefiting you.

Next, try sending Unicode filenames with dfiles->fWide set to TRUE and see if it makes a difference. Some apps do not process Ansi data. Windows is a Unicode-based OS. Use IsWindowUnicode() to know whether a given HWND expects Ansi or Unicode window messages.

Lastly, some apps simply do not implement WM_DROPFILES (they do not call DragAcceptFiles() or enable WS_EX_ACCEPTFILES). The preferred way to handle drag&drop in modern Windows versions is to implement the IDropTarget interface instead, and associate it with an HWND using RegisterDragDrop(). There is no API to retrieve an HWND's IDropTarget, but it can be done manually:

(adapted from this discussion: How to receive for HWND it IDropTarget?)

IDropTarget* GetRegisteredDropTargetFromWnd (HWND hWnd) 
{ 
    IUnknown *pBuffer = (IUnknown *) GetProp (hWnd, TEXT("OleDropTargetInterface")); 
    if (pBuffer != NULL) 
    { 
        IDropTarget *pRetVal = NULL; 
        if (SUCCEEDED(pBuffer->QueryInterface(IID_IDropTarget, (void **) &pRetVal)))
            return pRetVal;     
    }
    return NULL; 
}

If an HWND has an IDropTarget, you can wrap your DROPFILES data with an IDataObject and pass it to the IDropTarget::Drop() method. If Drop() accepts the data, do not post a WM_DROPFILES message. The trick, however, is that the IDropTarget* pointer returned by GetProp() is relative to the process that owns the HWND, so you would have to marshal it into your process, or else inject your code into the HWND's process, in order to actually use the interface pointer.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • The code for retrieving `IDropTarget` looks like it wouldn't work cross-process (at least, the value retrieved wouldn't be meaningful). If the target app registers a drop target (as described [here](http://blogs.msdn.com/b/oldnewthing/archive/2010/05/03/10006065.aspx)) you could use that. – arx Mar 08 '14 at 19:22
  • If you read the discussion I linked to, the `IDropTarget` retrieval code was determined by analyzing the inner workings of `DoDragDrop()`, which works with `IDropTarget` across process boundaries. Other online sources, including [here](http://social.msdn.microsoft.com/Forums/en-US/f2792332-14ea-4e46-8bbf-c900375ca2ea/outlook-shell-extensions?forum=outlookdev) and [here](http://www.codeguru.com/cpp/i-n/internet/email/article.php/c3381/Handling-Drag-and-Drop-of-Email-Attachments.htm), confirm this. – Remy Lebeau Mar 09 '14 at 03:28
  • In your first link, the commenter who suggests using this method also says (in a later comment) that you need to run in the same address space. I know that `DoDragDrop` works cross-process; I also know that it uses OLE code that runs in every process. Finally, how would it possible that *every* registered `IDropTarget` pointer was valid in the address space of *every* process? – arx Mar 09 '14 at 10:47
  • Remy, your answer is inline with a suggestion I have had in another related question; I have a "half-baked" attempt at doing exactly this on my home computer... maybe I should go this route instead then, huh? Even if only for the increased compatibility with modern applications? The code I have so far uses the "Createremotethread" method of injecting a DLL into the target application (which is only one app - on one computer - so I'm not worried that Createremotethread isn't backward compatible).. Is this a suitable vector to achieve this solution? Then pass the IDataObject within the process? – araisbec Mar 09 '14 at 14:00
  • @araisbec: I already [described](http://stackoverflow.com/questions/21937330/simulate-windows-drag-and-drop-with-code/21938622#21938622) simple algorithm of injection in the answer to your previous question. Why don`t you use it? – Denis Anisimov Mar 09 '14 at 15:14
  • Mainly because I already had the code finished and understood for the above method... but I'm still struggling with it, so I may give your method another shot. – araisbec Mar 09 '14 at 16:57
  • The only problem with hooking/injection is that you have to take into account whether the target process is 32bit or 64bit. For hooking, you need separate. 32bit/64bit DLLs. For injection, you don't need DLLs but you still need separate 32bit/64bit code to inject. Either way, the premise is the same. Get your code to run in the target process, serialize the filename(s) into that process, then create `IDataObject` and call `IDropTarget` within that process. – Remy Lebeau Mar 09 '14 at 21:54
  • The 64/32 bit thing is actually a non-issue, as I am working with ONE specific program on one computer, and I know it is 32bit. Remy (and Denis), I'm currently implementing your solution, however I am confused about two things: A) Once I have injected the DLL, can I simply run the code I need to run from within the DLL's dllmain (that is called when it is loaded)? Or should I be running it from another scope (since dllmain exits once the DLL has been loaded). See next comment for the next question... – araisbec Mar 16 '14 at 14:40
  • B) I am admittedly a little confused about how to wrap my filenames using IDataObject (I know what an interface is, but have never used one before)... If I am understanding this properly, should I be wrapping the filenames in a "DataObject" (which implements the IDataObject interface), and somehow use the "FileDrop" data format? This is my first time using C++ (and the WinAPI) for any sort of real project... and I also apologize for taking a while to update this thread, as I have just started my full-time career, and this is all part of a project that I am trying to squeeze in for my father. – araisbec Mar 16 '14 at 14:44
  • It is very dangerous to run most code inside of dllmain() itself. Only a small subset of APIs are safe to used in dllmain(). What you can do, however, is have dllmain() spawn a worker thread to then run your main code. – Remy Lebeau Mar 16 '14 at 18:04
  • You need to write a class that [implements IDataObject](http://msdn.microsoft.com/en-us/library/windows/desktop/bb776903.aspx). That class's GetData(), GetDataHere(), and QueryGetData() methods can then provide the filenames using the standard [CF_HDROP](http://msdn.microsoft.com/en-us/library/windows/desktop/bb776902.aspx#CF_HDROP) format. – Remy Lebeau Mar 16 '14 at 18:08
0

I have used code almost identical to yours with good results with many applications. I think your problem is that the window you find with FindWindow() is a top-level window, which may not be the window enabled for dropping in the target app. Some apps only enable selected child windows for dropping. The problem of course is finding that window. I have not found any simple solution to this problem. You could recursively enumerate all children of the top window using EnumChildWindows() and trying to identify the correct window (i.e., by class, ID, Window style, or other parameter) but that's gross. I use SpyXX but that's not a great solution either.
Good Luck.

DontPanic
  • 2,164
  • 5
  • 29
  • 56
  • This problem (finding drop target) piqued my curiosity. I came up with a solution. Here's what I did: 1. Find top level window (e.g. FindWindow()). 2. Get that window offset (screen) and size. 3. Create a grid (say 10x10) of coordinates within the window. 4. For each grid point, call WindowFromPoint() to retrieve the window (or child) at that point. 5. Get Ex Style (i.e. GetWindowLong( GWL_EXSTYLE)). 6. Mask with WS_EX_ACCEPTFILES. 7. If set, this is the (first) window that will accept files. Only FIRST target will be found. Use smaller grid pitch if target wnd is very small. – DontPanic Sep 12 '15 at 16:37