如何在C++17中用std::filesystem处理Windows长路径
Windows 10/11下C++长路径遍历Unicode文件崩溃的解决方法
在Windows 10/11系统中,为处理长路径给目录添加L"\\\\?\\"前缀后,使用C++17 std::filesystem或boost::filesystem遍历目录时,遇到含Unicode字符的文件会崩溃;不加前缀则能正常运行。相关代码如下:
void IterateDir(const std::string& newDir) { namespace fs = std::filesystem; fs::path rootPath; std::wstring wpath(newDir.begin(), newDir.end()); wpath = std::wstring(L"\\\\?\\") + wpath; rootPath.assign(wpath); for (auto iter = fs::recursive_directory_iterator(rootPath); iter != fs::recursive_directory_iterator(); ++iter) { try { auto file = *iter; std::string filename = file.path().filename().string(); std::cout << "\nfile: " << filename; } catch (const std::exception& e) { std::cout << "Exception while iterating directory." + std::string(e.what()); } catch (...) { std::cout << "Unknown exception while iterating directory."; } } }
解决方法
方法1:启用Windows原生长路径支持(无需手动加前缀)
Windows 10 1607及后续版本、Windows 11支持原生长路径,无需依赖\\\\?\\前缀。可以通过两种方式开启:
- Manifest配置:在项目中添加包含以下内容的manifest文件,让程序声明支持长路径:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> </windowsSettings> </application> </assembly> - 全局注册表开启(需管理员权限):打开注册表编辑器,定位到
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem,将LongPathsEnabled的DWORD值设为1。
启用后,std::filesystem可以直接处理超过260字符的路径,同时完美兼容Unicode字符,无需额外修改代码。
方法2:修正路径前缀的编码与拼接逻辑
如果必须使用\\\\?\\前缀,需要解决原代码中的编码错误和路径拼接问题:
- 正确转换窄字符串到宽字符串:原代码中直接通过
std::wstring wpath(newDir.begin(), newDir.end())转换会丢失Unicode字符的多字节编码信息,导致转码错误。改用Windows API实现正确的UTF-8到宽字符转换:
调用该函数替代原代码中的直接构造,确保Unicode字符正确转码。std::wstring StringToWide(const std::string& str) { int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); std::wstring wstr(len, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], len); return wstr; } - 保证路径前缀拼接正确:
\\\\?\\前缀仅支持绝对路径,拼接后需保留盘符后的反斜杠(如\\\\?\\C:\\test),如果传入的newDir是相对路径,需要先转为绝对路径再添加前缀。
方法3:直接使用Windows原生API遍历
绕开标准库的问题,直接调用FindFirstFileW和FindNextFileW API,这些API对\\\\?\\前缀和Unicode字符的支持更稳定:
#include <windows.h> #include <string> #include <iostream> std::wstring StringToWide(const std::string& str) { int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); std::wstring wstr(len, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], len); return wstr; } void IterateDirWinAPI(const std::wstring& wDir) { std::wstring searchPath = wDir + L"\\*"; WIN32_FIND_DATAW findData; HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData); if (hFind == INVALID_HANDLE_VALUE) { std::cout << "FindFirstFile failed, error code: " << GetLastError() << std::endl; return; } do { if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0) { continue; } std::wstring fullPath = wDir + L"\\" + findData.cFileName; // 转换宽字符文件名到UTF-8窄字符串输出 char filenameBuf[512]; WideCharToMultiByte(CP_UTF8, 0, findData.cFileName, -1, filenameBuf, sizeof(filenameBuf), nullptr, nullptr); std::cout << "file: " << filenameBuf << std::endl; // 递归遍历子目录 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { IterateDirWinAPI(fullPath); } } while (FindNextFileW(hFind, &findData)); FindClose(hFind); } // 外部调用入口 void IterateDir(const std::string& newDir) { std::wstring wpath = StringToWide(newDir); wpath = L"\\\\?\\" + wpath; IterateDirWinAPI(wpath); }
内容的提问来源于stack exchange,提问作者BitKnight
相关产品推荐
相关产品推荐

