GetFinalPathNameByHandle可获取句柄对应的真实路径,用于识别Windows保留设备名(如.CON、?GlobalRoot...),需配合CreateFile(带FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)及前缀校验,不可依赖std::filesystem。

Windows下用 GetFinalPathNameByHandle 检查是否指向保留设备名
系统保留路径(如 \.CON、\.PRN、\?GlobalRootDeviceHarddiskVolumeShadowCopy1)不是普通文件路径,直接用 GetFileAttributes 或 std::filesystem::exists 会失败或返回误导结果。真正可靠的方式是打开句柄后获取其最终解析路径,再比对 Windows 保留设备前缀。
关键点:必须用 \. 或 \? 前缀调用 CreateFile,否则 API 会自动过滤掉设备名;且需以 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS 打开,否则某些保留路径(如卷影副本路径)打不开。
- 先调用
CreateFile获取句柄,失败则大概率不是有效保留路径(但也可能是权限问题) - 成功后调用
GetFinalPathNameByHandle,若返回以\??\开头的字符串,再检查其后是否为CON、PRN、AUX、NUL、COM1–COM9、LPT1–LPT9等经典 DOS 设备名 - 注意
GetFinalPathNameByHandle返回的\??\是内核对象路径,\??\GLOBALROOT开头的也属于系统级保留路径(如卷影副本、符号链接目标)
std::filesystem::path 无法识别保留路径,别依赖它
std::filesystem::exists 对 .CON 返回 false,对 ?C: 返回 true,但两者都是合法系统路径——这说明标准库只做“文件存在性”判断,不处理 NT 对象命名空间语义。它甚至无法解析 .PHYSICALDRIVE0 这类路径,会抛出 std::filesystem::filesystem_error。
- 不要用
path.has_filename()或path.parent_path().empty()来推断是否为保留路径——\.后跟任意名字都可能合法 -
std::filesystem::canonical在遇到\.路径时通常失败,不是 bug,是设计如此 - 若业务逻辑需要统一处理路径,应在调用标准库前先做
\./\?前缀检测,并分流到 Win32 API 分支
常见误判场景和绕过方法
开发者常把 C:$Recycle.Bin、C:System Volume Information 当作“系统保留路径”,其实它们只是受 ACL 保护的普通目录,不是 NT 对象命名空间中的保留名。真正的保留路径必须能被 CreateFile 以设备语义打开。
立即学习“C++免费学习笔记(深入)”;
-
\.C:是合法保留路径(代表卷设备对象),但C:不是——前者可打开,后者不能 -
\localhostc$是网络路径,不属于本地系统保留路径范畴,GetFinalPathNameByHandle会失败 - 用户自建的
\?MyAppData符号链接不会被识别为保留路径,除非它最终解析到\??\下的真实设备路径 - 如果只需阻止用户输入危险路径,简单匹配前缀
"\\.\\"、"\\?\\"、"\\?\UNC\\"已覆盖 95% 场景,无需走完整句柄流程
跨平台代码里怎么写才不踩坑
Linux/macOS 没有等价概念,所谓“保留路径”是 Windows NT 内核特有机制。因此任何跨平台抽象层(比如 Qt 的 QDir、Boost.Filesystem)都不可能提供统一接口。硬要封装,只能 Windows 分支用 GetFinalPathNameByHandle,其他平台直接返回 false。
- 别在头文件里声明一个通用函数
is_system_reserved_path(const std::string&)并期望它在 Linux 上有意义 - 构建时用
#ifdef _WIN32隔离逻辑,避免链接器找不到GetFinalPathNameByHandle - 若项目已用 C++17
std::filesystem,记得在 Windows 上 linkShlwapi.lib(GetFinalPathNameByHandle所在库),否则链接失败
FILE_FLAG_BACKUP_SEMANTICS,\?GlobalRoot 类路径就打不开;少一个 FILE_FLAG_OPEN_REPARSE_POINT,符号链接目标就解析不全。这两个标志不是可选的,是必要条件。


















