6

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:

  • 32-bit -> 32-bit : success
  • 64-bit -> 64-bit : success
  • 64-bit -> 32-bit : success
  • 32-bit -> 64-bit : fail

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:

  • If I drag-and-drop a file from a 64-bit executable to my 64-bit application, wParam has a value such as 0x000000F211C000B8 (which shows that there is no cast issue).
  • Next, without closing my application, if I drag the file from a 32-bit executable, wParam will have something like 0x0000000011C000B8 or 0xFFFFFFFF11C000B8, which means that the high order 32 bits are invalid.
  • If I replace the invalid high order by a valid one from a previous message (in this example, this would be 0x000000F2), then DragQueryFile works!

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

RbMm
  • 31,280
  • 3
  • 35
  • 56
v77
  • 216
  • 1
  • 14
  • Please tag either C or C++: it's likely to attract downvotes if you keep it like this. Also tag windows API. – Bathsheba Sep 21 '16 at 09:30
  • Please post [SSCCE](http://sscce.org). – Jesper Juhl Sep 21 '16 at 09:33
  • 1
    Just guessing here. Apparently that particular function has a special case for value 0xFFFFFFFF. If the code is using that feature and someone wrote code like `~0` to get this value, then the code might turn non-portable when `int` sizes change. – Lundin Sep 21 '16 at 09:41
  • `int` and, bizarrely, `long` stay at 32 bit on 64 bit windows. I kid you not. – Bathsheba Sep 21 '16 at 09:42
  • Is this related? http://www.eightforums.com/general-support/23889-cant-drag-files-explorer-into-64-bit-programs.html – Bathsheba Sep 21 '16 at 09:44
  • 1
    @Bathsheba: That's not particularly bizarre. – Lightness Races in Orbit Sep 21 '16 at 09:55
  • @Lundin: There is no `int` in the function signature. You are referring to a parameter of type [`UINT`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751.aspx#UINT), which is defined to be 32 bits on every platform. At any rate, we need to see a [mcve]. – IInspectable Sep 21 '16 at 09:57
  • @LightnessRacesinOrbit: perhaps we've gotten used to it. A `long` being 32 bit on a 64 bit OS seems perverse to me. `sizeof(long)` being less than `sizeof(sizeof(long))` is weird. Still it made porting my stuff from i686 to x64 a doddle ;-) – Bathsheba Sep 21 '16 at 09:59
  • @Bathsheba: [Why did the Win64 team choose the LLP64 model?](https://blogs.msdn.microsoft.com/oldnewthing/20050131-00/?p=36563) (Official documentation: [Abstract Data Models](https://msdn.microsoft.com/en-us/library/windows/desktop/aa384083.aspx)). – IInspectable Sep 21 '16 at 10:00
  • @Bathsheba: `long` does not mean `longest`, and that portability argument is precisely why things are the way they are. We should all be using fixed-width types anyway, except for things like pointer range... which _is_ 64-bit. – Lightness Races in Orbit Sep 21 '16 at 10:01
  • This sounds like you are truncating and then widening your `wParam`, with sign extension. Which then leads to invalid looking data. Anyway, without the code (ideally a [mcve]), there's nothing we can do. – IInspectable Sep 21 '16 at 15:04
  • 1
    @IInspectable - i test this by self and got same result with WM_DROPFILES - high bits of wParam all set. so this is windows bug. however another way with RegisterDragDrop is work well – RbMm Sep 21 '16 at 15:17
  • @IInspectable: I explain exactly the opposite: the 0xF2 I get proves that I have the whole 64 bits. And this is the same executable used for the 3 steps. – v77 Sep 21 '16 at 15:21
  • @v77: I don't see how that *"proves"* anything. There could be lots of reasons, why you see purportedly invalid data. Without the code, this question simply isn't useful. And with that last edit I would like to invite you to take the [tour] and visit the [help] to understand, what Stack Overflow is about, and why the code matters (even if you insist that it wouldn't). – IInspectable Sep 21 '16 at 15:29
  • 1
    Not useful? As I said, a lot of softwares are affected by this issue. This means that a lot of developers could be interested by an answer. But do as you wish... – v77 Sep 21 '16 at 15:40
  • @v77 - i test this also in WM_DROPFILES - high 32 bits of wParam always all 0 or 1 - so say about sign extend 32 bit value to 64. and in both case incorrect – RbMm Sep 21 '16 at 16:01
  • @v77: The **question** (as currently written) is not useful. This does not mean that a potential answer wouldn't be. But you have to go by the rules. Since you opted against learning about them, chances for this question to get re-opened are slim. You have the power to [change the odds](http://stackoverflow.com/posts/39612616/edit). – IInspectable Sep 21 '16 at 16:25
  • @IInspectable - strange - are you try by self do some research/coding on this ? why you decide that is v77 bug, but not windows bug ? because this is not documented in msdn ? – RbMm Sep 21 '16 at 16:52
  • 1
    The question will never be reopened, no matter what I do. But the message is wrong: I never asked debugging help. A lot of developers are concerned by the same issue, so it doesn't matter that my implementation is correct or not. Moreover, a code extract is not more relevant than putting my 900 lines of code. – v77 Sep 21 '16 at 16:52
  • @v77 - this is very easy reproduce if you really programmer but not only read documentation . i paste minimal example for test – RbMm Sep 21 '16 at 16:55
  • @RbMm: You are reading something into my comments, that isn't there. I never claimed either way. I'm just **repeatedly** asking the OP to provide the code they are using, to eliminate programming errors as the source. Those are the rules around here, and they are here for good reasons (essentially to make Q&A's better). Questions **do** get re-opened. This probably never happened to you (or v77), because you never decided to play by the rules (like v77). – IInspectable Sep 21 '16 at 16:59
  • @IInspectable - "This sounds like you are truncating and then widening your wParam, with sign extension" ""Questions seeking debugging help" - however you even not try check this by self. but this very easy without OP src code. – RbMm Sep 21 '16 at 17:03
  • @IInspectable - and if you read my comments - i claim that this is not v77 error but windows bug. and i paste self minimal code example in place v77 – RbMm Sep 21 '16 at 17:05
  • @RbMm: *"however you even not try check this by self"* - Of course not. There wasn't any code to try. If I wrote a test case, there would be no way of knowing, whether I was testing the same code as the OP. This is neither efficient, nor effective. And since you still haven't done so, please take the [tour] and spend some quality time in the [help]. Maybe you'll learn, why rules are the way they are. – IInspectable Sep 21 '16 at 17:14
  • @IInspectable - i also not have any code from v77. but i unlike you just fill something interesting in this case - write code by self and test. are you still cannot understand that task NOT IN v77 code or my code. you can test this by self (if you can write code of course ) and got the same result – RbMm Sep 21 '16 at 17:18
  • @RbMm: You still don't understand, why the OP needs to post their code, and you refuse to learn (by avoiding the [tour], for example). At any rate, my time **does** have value (unlike yours, it appears). If you need to talk back, you get to have the last word. I would much rather hope you wouldn't, and use that time at the [help] instead. – IInspectable Sep 21 '16 at 17:24
  • @IInspectable - i post self code, which reproduce problem - you can view it - this is not enough for you ? – RbMm Sep 21 '16 at 17:27
  • @IInspectable As I said, I can't put 900+ lines of codes. But as RbMm said, you can easily write your own code if you already worked on Win32 GUIs. And if you so much want know what is my code, you can download the source [here](https://sourceforge.net/projects/imdisk-toolkit/files/20160917/). – v77 Sep 21 '16 at 17:32
  • @v77: *"I can't put 900+ lines of codes"* - precisely the reason why I kept asking for a [mcve]. Like RbMm, you should take the [tour] as well, since you skipped over it, and are apparently quite confused, what Stack Overflow is. – IInspectable Sep 21 '16 at 17:35
  • @IInspectable As I said, it doesn't matter that my implementation is correct or not, because many developers are facing the same issue. It's not a problem caused by MY code, but by the code of many developers (and seemingly the code of Windows). Anyway, your example is here. What's wrong with it? Did you already work with DragAcceptFiles? – v77 Sep 21 '16 at 17:57
  • @v77: *"What's wrong with it?"* - The code doesn't match the question. Where is `DragQueryFile` called? At that level of cooperation, I have nothing to contribute. Good luck. – IInspectable Sep 21 '16 at 18:02
  • @IInspectable: "Where is DragQueryFile called?" Nowhere, so what? You missed my first edit: the bug is around wParam, not DragQueryFile. RbMm confirmed that. – v77 Sep 21 '16 at 18:26
  • @v77: the code shown is not valid in 32bit, by virtue of the fact that `WindowProc()` is missing the `CALLBACK` (`__stdcall`) calling convention. The default calling convention in C/C++ is `__cdecl`, so this code shouldn't even compile as shown, unless you are using a non-standard compiler configuration. In 64bit, it doesn't matter since 32bit calling conventions are ignored. – Remy Lebeau Sep 21 '16 at 18:38
  • @v77: `WPARAM` is 64bits in a 64bit process, but its value in `WM_DROPFILES` is an `HDROP` handle, which is a 32bit value even in a 64bit process. Per [Interprocess Communication Between 32-bit and 64-bit Applications](https://msdn.microsoft.com/en-us/library/aa384203.aspx): "*64-bit versions of Windows use 32-bit handles for interoperability. When sharing a handle between 32-bit and 64-bit applications, only the lower 32 bits are significant, so it is safe to truncate the handle (when passing it from 64-bit to 32-bit) or sign-extend the handle (when passing it from 32-bit to 64-bit)...*" – Remy Lebeau Sep 21 '16 at 18:42
  • @v77: "*... Handles that can be shared include handles to user objects such as windows (HWND), handles to GDI objects such as pens and brushes (HBRUSH and HPEN), and handles to named objects such as mutexes, semaphores, and file handles.*" So, in your 64bit process, the `HDROP` value in `wParam` has simply been sign-extended to 64bits when originating from a 32bit process. That is OK. It is safe to truncate the value back to 32bits when casting it to `HDROP` to pass it to `DragQueryFile()`. – Remy Lebeau Sep 21 '16 at 18:51
  • @Remy Lebeau: HDROP truncates nothing, as it is a handle and therefore a 64-bit value in a 64-bit executable. As shown in the first edit, I get properly the 64-bit value from wParam. It's not the case only when the dropped file comes from a 32-bit executable. Despite that, if I replace the high order 32 bits, DragQueryFile works. The truncation is done before coming into the window procedure. Why Windows would send the correct data without the proper handle? But who cares? Even if I found a workaround, it will will disapear with the question. – v77 Sep 21 '16 at 19:18
  • @RemyLebeau - " The default calling convention.." - i use __stdcall as default, and not set it explicit. so what ? you can easy correct it. but task not in this – RbMm Sep 21 '16 at 19:19
  • @RemyLebeau - are you still cannot understand that this is windows bug ?? wParam passing to WindowProc already invalid. only low 32 bit is correct. high 32 bit always all 0 or 1 – RbMm Sep 21 '16 at 19:22
  • 2
    I can verify this issue; `DragQueryFile` does seem to return 0 when passed an `HDROP` that came from a drag from 32 to 64 bits. Looks like a Windows bug. – Jonathan Potter Sep 21 '16 at 19:35
  • @JonathanPotter - task not in DragQueryFile but in invalid wParam in WM_DROPFILES message. only low 32 bit of it is valid. it is returned by GetMessage or PeekMessage. i add code snipet – RbMm Sep 21 '16 at 19:42
  • @RbMm I think you are barking up the wrong tree going on about the value of `wParam`. Concentrate on the fact that when passing the value that `WM_DROPFILES` gives you to the API functions, it doesn't work. The actual value is a black box. – Jonathan Potter Sep 21 '16 at 19:43
  • @JonathanPotter - no. you mistake. all task in invalid wParam from `WM_DROPFILES` . api function DragQueryFile here absolute not related - he got invalid parameter, as result and failed. but this is not they error – RbMm Sep 21 '16 at 19:45
  • Have it your way, your question will stay closed though. I am actually on your side though. – Jonathan Potter Sep 21 '16 at 19:45
  • @Jonathan Potter: the value of wParam is correct and allows to use DragQueryFile, it just lacks its high order 32 bits when a file is dropped from a 32-bit process. I need to find a way to retrieve it. – v77 Sep 21 '16 at 19:51
  • @v77 - you mistake. and contradicting yourself - " value of wParam is correct" - vs " just lacks its high order 32 bits" - all task in wParam - it incorrect. i have found similar bug many years ago on xp x64 (2003 x64) when invalid value returned by SetWindowLongPtr if debug registers active in thread. also high 32 bits is zeroed – RbMm Sep 21 '16 at 19:55
  • @v77: you clearly did not read the link I posted. I already told you what is happening (`HDROP` is a 32bit value that is being sign-extended to 64bits, thus the extra high 1 bits) and what you can do to address it (truncate the high 32bits before casting, eg: `#ifdef _WIN64 wParam &= 0x00000000FFFFFFFFull; #endif HDROP hDrop = (HDROP) wParam;` If you choose to ignore me, so be it. I said my piece. – Remy Lebeau Sep 21 '16 at 20:00
  • @Remy Lebeau: I have read your link, and a lot of others. [HDROP](https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx) is defined as a HANDLE, which is defined as a PVOID, that is, a pointer, that is, a 64-bit value in a 64-bit process. And if the high 32bits are 0, this does not work. – v77 Sep 21 '16 at 20:11
  • 1
    @RemyLebeau - you mistake- value lost self high 32 bit already in kernel (assume win32k.sys bug) say for example i got `5d640078` in wParam when correct value is `1c05d640078` http://i.imgur.com/AMZRtdJ.png – RbMm Sep 21 '16 at 20:16
  • @RemyLebeau - look - are you can read ? all what you say about 32-64 is UNRELATED here. how defined HDROP is unrelated. we already got incorrect 64bit value from kernel. so hard understand this ? so hard test this by self ? or if you not want or cannot test - why you so sure in self correct ? – RbMm Sep 21 '16 at 20:21
  • 1
    @RbMm: that fact (the high 32bits were not zero in the original kernel object and were being lost during the exchange) was not made clearer earlier. *However*, that being said, that fact contradicts the documentation: "*When sharing a handle between 32-bit and 64-bit applications, **only the lower 32 bits are significant**, so it is safe to truncate the handle (when passing it from 64-bit to 32-bit) or **sign-extend the handle (when passing it from 32-bit to 64-bit)**.*". The original handle should not be using the high 32bits. If you feel this is an OS bug, **report it to Microsoft!** – Remy Lebeau Sep 21 '16 at 20:21
  • @RemyLebeau - again documentation and all what you write unrelated here. the wParam (HDROP) is really point on some address in process heap. this is HGLOBAL. this is NOT convesion between 32 and 64. win32k.sys (i think) ALLOCATE same data in 64 bit process heap. and must return address of this memory. but it return only 32 bit of adress !!! high 32 bit lost(or random). in win10 process heap usually have adress > 0xffffffff - so address always incorrect if only 32 bit – RbMm Sep 21 '16 at 20:25
  • @Remy Lebeau: if you truncate the handle, even a file dropped from a 64-bit process will not work. The MSDN documentation is full of contradictions, and we have here a good example of that. – v77 Sep 21 '16 at 20:27
  • http://i.imgur.com/AMZRtdJ.png - yes, this is windows bug. i how say, many years ago view similar – RbMm Sep 21 '16 at 20:28
  • 1
    Fine, then simply stop using `WM_DROPFILES`. It is deprecated anyway (and has been for a long time). Use `IDropTarget` and `RegisterDragDrop()` instead, then you won't have this problem anymore. – Remy Lebeau Sep 21 '16 at 20:30
  • @RemyLebeau - yes, RegisterDragDrop + IDropTarget well work in 32->64 case – RbMm Sep 21 '16 at 20:31
  • @RemyLebeau for finish discussion - i try say last time - problem not related to extend 32bit value to 64bit. absolute another. somebody allocate DROPFILES structure in process heap (by GlobalAlloc) and return it value in wParam. but by mistake it return only 32-bit of this pointer. high 32 bit is random (i view all 1 or all 0 bits). begin from win 10 (or may be win 8) process heap always located at >0x100000000 address. so wParam (pointer to address in heap) is always incorrect. this is for clear understand root of the problem. – RbMm Sep 21 '16 at 20:39
  • Even if it's old, [WM_DROPFILES](https://msdn.microsoft.com/en-us/library/windows/desktop/bb774303(v=vs.85).aspx) is not marked as deprecated. – v77 Sep 21 '16 at 20:48
  • @RbMm: many thanks for your help. The closest value to wParam is given by GlobalAlloc(GMEM_MOVEABLE, 0), so I just have to extract its high order. I checked this behavior from XP to Win10. This will allow me to avoid the big and ugly OLE. :) – v77 Sep 22 '16 at 10:45
  • @v77 - yes, memory pointed by wParam (this is DROPFILES structure) really - allocated by call GlobalAlloc(LMEM_MOVEABLE, ) - however you got only low 32 bit of this address , when in win10 x64 process this heap base always have not zero high 32 bits. (this os special design). you can of course by self once allocate memory by GlobalAlloc(LMEM_MOVEABLE, ) and use(save) it high 32 bits - and then replace with it 32 high bits from wParam. this will be work now, but this is very ugly solution. i be use IDropTarget on your place – RbMm Sep 22 '16 at 10:50

2 Answers2

3

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.

v77
  • 216
  • 1
  • 14
0

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);
}
Niclet
  • 31
  • 2