我正在编写一个接受文件拖放的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);
        }
    }
    

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

    要进行测试,可以执行32位记事本,打开“打开文件”对话框,然后将任何文件拖放到我们的窗口中

    最佳答案

    问题重新开放后,我可以发布适当的答案。

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

    实际上,当我们将文件从32位应用程序拖到64位应用程序时,后者应该崩溃,因为我们提供了指向DragQueryFile()的错误指针。但事实并非如此,因为DragQueryFile()处理这些异常。

    现在,解决方案:

  • 使用IDropTarget界面。如果您不关心使用OLE并在可执行文件中添加大约10KB只是为了读取RAM中已经存在的文件名(这不是我的情况),那么这是一个很好的解决方案(Microsoft推荐)。
  • 找到一种方法来检索wParam的重要部分。如前所述,该值是指向堆的指针。最接近的值由GlobalAlloc(GMEM_MOVEABLE, 0)给出。它通常给出wParam的值(或者应该是wParam的值)+16。即使有时可能会稍高一些,这也足以检索wParam缺少的高阶32位。
    为了解决堆重叠4GB边界的可能性不大的情况,我们可以尝试通过在高阶32位中添加或删除1来进行尝试。
    请注意,GlobalFree()仍然是必需的。否则,每次调用GlobalAlloc()后,您将消耗几个字节(根据我的测试,为16个字节)。
  • 禁用High Entropy ASLR。这需要Windows 8或更高版本,这就是为什么此错误很少在Windows 7和更低版本上发生的原因。在Windows 7上,地址是随机的,但保持在4GB的限制内。
    就是说,由于符号扩展,您可能仍必须将高阶32位清零。并且该解决方案意味着安全性降低。
  • 关于c - 从32位拖放到64位,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39612616/

    10-13 08:26