I am writing a C program that accepts drag-and-drop of files. When it is compiled in 32-bit, it works in any case. But when it compiled in 64-bit, it works only for files dragged from a 64-bit application:
I still get the WM_DROPFILES message, but DragQueryFile returns nothing (the number of files is 0).
This seems to be an issue for a lot of applications but I would like to know if there is a workaround about that.
Edit:
So the data are here, somewhere, I just don't know how to retrieve them (at least without an ugly hack).
Edit 2:
I will provide no code because I assume that those who answer know something about this issue that affects a large number of softwares.
------ EDIT ----------
minimal code that reproduces it
LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WCHAR sz[32];
switch (uMsg)
{
case WM_DROPFILES:
swprintf(sz, L"%p", wParam);// look for wParam
MessageBox(0,0,sz,0);
break;
case WM_NCCREATE:
DragAcceptFiles(hwnd, TRUE);
break;
case WM_NCDESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void minimal()
{
static WNDCLASS wndcls = { 0, WindowProc, 0, 0, 0, 0, 0, 0, 0, L"testwnd" };
if (RegisterClass(&wndcls))
{
if (HWND hwnd = CreateWindowEx(WS_EX_ACCEPTFILES, wndcls.lpszClassName, 0,
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, 0, 0, 0))
{
MSG msg;
while (0 < GetMessage(&msg, 0, 0, 0))
{
if (msg.message == WM_DROPFILES)
{
// look for msg.wParam returned by GetMessage
WCHAR name[256];
DragQueryFile((HDROP)msg.wParam, 0, name, RTL_NUMBER_OF(name));
}
DispatchMessage(&msg);
}
}
UnregisterClass(wndcls.lpszClassName, 0);
}
}
interesting that if call DragAcceptFiles (even only jump on first it instruction) high 32 bits of wParam will be all 1. if not call it, by set WS_EX_ACCEPTFILES exstyle by self - all high bits of wParam will be 0
for test can exec 32 bit notepad, open Open File Dialog and drag-drop any file to our window
As the question has been reopened, I can post a proper answer.
This is truly a bug of Windows. In a 64-bit process, wParam is a 64-bit value and is used as is to send a "HDROP", which is in fact a pointer to a pointer to a DROPFILES structure. The tests showed that the shell uses the whole 64 bits, and writes the data into the heap. If a file is dragged from a 32-bit application, the data are still properly written into the heap, even if the latter is located above 4GB. But despite that, in this case, wParam is converted to a 32-bit value, and then sign-extended to 64-bit.
In fact, when we drag a file from a 32-bit application to a 64-bit one, the latter is supposed to crash, because we provide an incorrect pointer to DragQueryFile()
. But it does not, because DragQueryFile()
handles these exceptions.
Now, the solutions:
Use the IDropTarget interface. This is a good solution (and recommended by Microsoft) if you don't care about using OLE and adding about 10KB in your executable only for reading a file name that is already in RAM (it's not my case).
Find a way to retrieve the high part of wParam. As explained, this value is a pointer to the heap. The closest value is given by GlobalAlloc(GMEM_MOVEABLE, 0)
. It usually gives the value of wParam (or the one it is supposed to have) +16. Even if it can be sometimes slightly higher, this is enough to retrieve the lacking high order 32 bits of wParam.
To cover the unlikely case where the heap overlaps a 4GB boundary, we can try by adding or removing 1 to the high order 32 bits.
Note that GlobalFree()
remains required. Otherwise, you consume a few bytes (16 according to my tests) after each call to GlobalAlloc()
.
Disable the High Entropy ASLR. This one requires Windows 8 or later, and that's why this bug rarely occurs on Windows 7 and prior. On Windows 7, the addresses are randomized, but remain under the 4GB limit. That said, you may still have to zero the high order 32 bits, because of the sign extension. And this solution means a security decrease.
While I had same issue regarding drag&drop of files from 32bit app to 64bit app, I have found a solution which seems to be more reliable than previous one, but greatly inspired from it.
I enumerate all the memory block of the process heap until I find a match with truncated WPARAM.
Hope it can help.
HDROP hDrop = NULL;
HANDLE hProcessHeap = ::GetProcessHeap();
if (NULL != hProcessHeap && ::HeapLock(hProcessHeap))
{
PROCESS_HEAP_ENTRY heapEntry = { 0 };
while(::HeapWalk(hProcessHeap, &heapEntry) != FALSE)
{
if ((heapEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0)
{
HGLOBAL hGlobal = ::GlobalHandle(heapEntry.lpData);
// Assuming wParam is the WM_DROPFILES WPARAM
if ((((DWORD_PTR) hGlobal) & 0xFFFFFFFF) == (wParam & 0xFFFFFFFF))
{
hDrop = (HDROP) hGlobal; // We got it !!
break;
}
}
}
::HeapUnlock(hProcessHeap);
}
User contributions licensed under CC BY-SA 3.0