从32位拖放到64位

v77*_*v77 6 c winapi drag-and-drop

我正在编写一个接受文件拖放的C程序.当它以32位编译时,无论如何都适用.但是当它以64位编译时,它仅适用于从64位应用程序拖动的文件:

  • 32位 - > 32位:成功
  • 64位 - > 64位:成功
  • 64位 - > 32位:成功
  • 32位 - > 64位:失败

我仍然得到WM_DROPFILES消息,但DragQueryFile没有返回任何内容(文件数为0).

这似乎是许多应用程序的问题,但我想知道是否有解决方法.

编辑:

  • 如果我将文件从64位可执行文件拖放到我的64位应用程序,wParam有一个值,如0x000000F211C000B8(表明没有强制转换问题).
  • 接下来,在不关闭我的应用程序的情况下,如果我从32位可执行文件中拖动文件,wParam将具有类似0x0000000011C000B8或0xFFFFFFFF11C000B8的内容,这意味着高位32位无效.
  • 如果我用前一条消息中的有效高位替换无效高位(在本例中,这将是0x000000F2),那么DragQueryFile可以工作!

所以数据在这里,某处,我只是不知道如何检索它们(至少没有丑陋的黑客).

编辑2:

我将不提供任何代码,因为我认为那些回答的人知道这个影响大量软件的问题.

------编辑----------

重现它的最小代码

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果调用DragAcceptFiles(甚至只跳过它的第一个指令)高位32位的wParam将全部为1.如果不调用它,通过设置WS_EX_ACCEPTFILES exstyle by self-所有高位wParam将为0

for test可以执行32位记事本,打开Open File Dialog并将任何文件拖放到我们的窗口

v77*_*v77 3

由于问题已重新提出,我可以发布正确的答案。

这确实是 Windows 的一个错误。在64位进程中,wParam是一个64位值,按原样用于发送“HDROP”,它实际上是一个指向DROPFILES结构的指针。测试表明shell使用了整个64位,并将数据写入堆中。如果从 32 位应用程序中拖动文件,数据仍然会正确写入堆中,即使后者位于 4GB 以上。但尽管如此,在本例中,wParam 被转换为 32 位值,然后符号扩展为 64 位。

事实上,当我们将文件从 32 位应用程序拖到 64 位应用程序时,后者应该会崩溃,因为我们提供了一个不正确的DragQueryFile(). 但它没有,因为DragQueryFile()处理这些异常。

现在,解决方案:

  • 使用IDropTarget接口。如果您不关心使用 OLE 并在可执行文件中添加大约 10KB 仅用于读取 RAM 中已有的文件名(这不是我的情况),那么这是一个很好的解决方案(并且由 Microsoft 推荐)。

  • 找到一种方法来检索 wParam 的高位部分。正如所解释的,该值是指向堆的指针。最接近的值由 给出GlobalAlloc(GMEM_MOVEABLE, 0)。它通常给出 wParam 的值(或者它应该具有的值)+16。即使有时可能稍高,这也足以检索 wParam 缺少的高位 32 位。为了应对堆与 4GB 边界重叠的不太可能的情况,我们可以尝试向高位 32 位添加或删除 1。请注意,GlobalFree()仍然是必需的。否则,每次调用GlobalAlloc().

  • 禁用高熵 ASLR。此问题需要 Windows 8 或更高版本,这就是为什么此错误很少出现在 Windows 7 及更早版本上。在 Windows 7 上,地址是随机的,但仍低于 4GB 限制。也就是说,由于符号扩展,您可能仍然需要将高位 32 位清零。而这种解决方案意味着安全性的降低。