Windows下Qt拖拽超长文件路径返回空数组,如何正确获取?
问题描述
在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

