You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows下Qt拖拽超长文件路径返回空数组,如何正确获取?

Qt Windows拖放长路径问题:正确处理方式与原因解析

问题描述

在Windows系统中使用Qt处理文件拖放时,遇到路径长度超过260字符MAX_PATH限制的文件会出现异常:QMimeData::hasUrls()返回true,但调用urls()得到的列表为空。查看内部逻辑发现,这是因为Qt调用的COM函数返回ERROR_INSUFFICIENT_BUFFER,导致无法获取路径数据。

但奇怪的是,从QMimeData中请求"application/x-qt-windows-mime;value="Shell IDList Array""类型的数据可以成功获取,且里面包含了所需的路径信息。我通过逆向解析该数据结构写了一段临时代码来拼凑文件名,但这方案非常粗糙,想知道正确的处理方式是什么?Qt为什么会出现这个问题?

临时拼凑代码

static QString evilWindowsMimeDataHack(QMimeData const *mimeData)
{
#define IsAligned(n) (static_cast<size_t>((reinterpret_cast<uintptr_t>(n) & 1) == 0))
    QByteArray shIdList = mimeData->data(uR"(application/x-qt-windows-mime;value="Shell IDList Array")"_s);
    if (shIdList.size() < 8LL + 35LL + 2LL)
        return {};

    char const *data = shIdList.constData();
    char const *end  = data + shIdList.size();
    char const *last = data + *reinterpret_cast<uint32_t const *>(data + 8);
    data += 35;

    // 驱动器号是纯ASCII字符串,理论上可以是多个字符,所以按字符串处理
    // 出于谨慎使用strnlen,它是非标准函数但Windows上肯定存在
    QString str = uR"(\\?\)" + QString::fromLatin1(data, qsizetype(strnlen(data, end - data)));

    // 后续会盲目添加路径分隔符,所以确保当前末尾没有
    if (str.endsWith(u'\\'))
        str.chop(1);
    data += str.size() + 1;
    if (data >= end)
        return {};
    // 跳过28字节未知数据,如果当前是偶数偏移则跳过29字节(数据似乎总是对齐到奇数偏移)
    data += 28 + IsAligned(data);

    while (data + 2 < end) {
        // ASCII短文件名(以null结尾)
        data += strnlen(data, end - data) + 1;
        // 跳过46字节未知数据,如果当前是偶数偏移则跳过47字节
        data += 46 + IsAligned(data);
        if (data >= end)
            break;

        // UTF-16路径段(以null结尾),Qt不接受未对齐的字符串,所以先复制到缓冲区
        // 同样出于谨慎使用wcsnlen
        size_t len = wcsnlen(reinterpret_cast<wchar_t const *>(data), end - data);
        auto  *tmp = new char16_t[len + 1];
        memcpy(tmp, data, (len + 1) * sizeof(wchar_t));
        str.append(u'\\');
        str.append(reinterpret_cast<QChar *>(tmp), qsizetype(len));
        delete[] tmp;

        // 跳过UTF-16数据和null终止符
        data += (len + 1) * sizeof(wchar_t);
        // 除了最后一个条目跳过18字节外,其他每个条目跳过16字节
        // `last`偏移比当前位置超前4字节
        data += data + 4 == last ? 18 : 16;
    }

    return str;
#undef IsAligned
}

正确处理方式

1. 使用Windows Shell API解析Shell IDList

直接利用Windows原生API处理Shell IDList Array数据,避免手动逆向解析:

  • 从QMimeData获取原始数据后,转换为LPITEMIDLIST指针
  • 调用SHGetPathFromIDListW宽字符版本函数提取路径,该函数支持长路径(需系统版本Windows 10 1607+,且应用程序清单声明longPathAware="true")

示例代码:

#include <shlobj.h>
#pragma comment(lib, "shell32.lib")

QString getLongPathFromMimeData(const QMimeData* mimeData) {
    QByteArray data = mimeData->data(u"application/x-qt-windows-mime;value=\"Shell IDList Array\""_s);
    if (data.isEmpty()) return {};

    LPITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(data.data());
    wchar_t path[8192] = {0}; // 分配足够大的缓冲区容纳长路径
    if (SHGetPathFromIDListW(pidl, path)) {
        return QString::fromWCharArray(path);
    }
    return {};
}

2. 升级Qt版本

Qt 5.15及以上版本、Qt 6.x系列已经修复了长路径拖放的问题:内部会自动处理ERROR_INSUFFICIENT_BUFFER错误,使用动态扩容的缓冲区获取路径数据,无需手动解析Shell IDList Array,直接调用QMimeData::urls()即可拿到完整的长路径URL。

Qt出现此问题的原因

旧版本Qt处理Windows拖放URL时,默认使用基于MAX_PATH的固定大小缓冲区调用COM接口。当路径长度超过该限制时,COM函数返回ERROR_INSUFFICIENT_BUFFER,而Qt未针对该错误做重试逻辑,导致路径数据获取失败,最终urls()返回空列表。

而Shell IDList Array是Windows Shell直接传递的原始数据结构,未经过Qt的路径截断处理,因此能保留完整的长路径信息。


内容的提问来源于stack exchange,提问作者Roflcopter4

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:54:53