如何在C++ Windows控制台应用程序中启用“长路径感知”的行为以设置当前目录。

4
在Windows上的C++控制台应用程序中,我正在尝试打破MAX_PATH限制,以便SetCurrentDirectoryW函数可以使用。
已经有很多类似的问题被问到了,但是没有一个可用的答案:

文档研究

显然,使用应用程序清单文件可能是可能的。SetCurrentDirectoryW的文档声明:

提示 从Windows 10版本1607开始,对于此函数的unicode版本(SetCurrentDirectoryW),您可以选择退出MAX_PATH限制。有关详细信息,请参见命名文件、路径和命名空间中的“最大路径长度限制”部分。

来自清单的一般文档

清单是伴随并描述并行程序集或隔离应用程序的XML文件。 ... 应用程序清单 描述 隔离的应用程序。它们用于在运行时管理应用程序应绑定到的共享并行程序集的名称和版本。应用程序清单被复制到与应用程序可执行文件相同的文件夹中,或作为应用程序可执行文件的资源包含在其中。

有关程序集清单的文档 再次指出与应用程序清单的区别:

作为DLL中的资源,程序集可供DLL私人使用。程序集清单不能作为EXE的资源包含在其中。EXE文件可以将 应用程序清单 包含为资源。

应用清单文档列出了程序集assemblyIdentity元素是必需的:

  • assembly element需要一个属性:

    • manifestVersion
      • 必须将manifestVersion属性设置为1.0。
  • assemblyIdentity element需要以下属性:

    • type
      • 值必须为Win32且全部小写。
    • name
      • 名称格式应为:Organization.Division.Name。例如,Microsoft.Windows.mysampleApp。
    • version
      • 指定应用程序或程序集版本。使用四部分版本格式:mmmmm.nnnnn.ooooo.ppppp。每个由句点分隔的部分都可以是0-65535(含)。有关详细信息,请参阅Assembly Versions

除此之外,所有其他元素和属性似乎都是可选的。

assembly element 的附加要求包括:

它的第一个子元素必须是 noInherit 或 assemblyIdentity 元素。 assembly 元素必须在命名空间 "urn:schemas-microsoft-com:asm.v1" 中。 assembly 的子元素也必须在该命名空间中,通过继承或标记实现。

最后,longPathAware element 是可选的,但有望允许 SetCurrentDirectoryW 使用长路径:

启用长度超过 MAX_PATH 的长路径。该元素支持 Windows 10 版本 1607 及更高版本。有关更多信息,请参阅this article

文档中的部分展示了这个XML清单的示例:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
 ...
  <asmv3:application>
    <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
      <ws2:longPathAware>true</ws2:longPathAware>
    </asmv3:windowsSettings>
  </asmv3:application>
 ...
</assembly>

它似乎并没有完全遵循程序集元素的规则:

程序集元素必须在命名空间“urn:schemas-microsoft-com:asm.v1”中。 程序集的子元素也必须通过继承或标记在此命名空间中。

测试

测试环境为:

  • Windows 10 21H2 x64 19044.1586
  • VS2022 17.1.1
  • Windows SDK 版本 10.0.20348.0

测试应用程序是一个新的C++控制台应用程序,在其中我对附加清单文件进行了以下更改: Additional Manifest Files

源代码非常简单;它也在godbolt上,但没有清单文件:
#include <iostream>
#include <string>
#include <windows.h> 

int main() {
    std::wstring const path = LR"(H:\test\longPaths\manySmallLongPaths\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\12345678\)";
    std::wstring const path2 = LR"(\\?\)" + path;
    if (!SetCurrentDirectoryW(path.c_str())) {
        printf("Exe SetCurrentDirectory failed 1 - (%d)\n", GetLastError());
        if (!SetCurrentDirectoryW(path2.c_str()))
            printf("Exe SetCurrentDirectory failed 2 - (%d)\n", GetLastError());
    }
}

尝试将所有内容整合在一起,我认为以下文件可能是一个有效的应用清单文件
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns:asmv1='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
    <asmv1:assemblyIdentity type='win32' name='my.test.app' version='1.0.0.0' />
    <asmv1:application>
        <asmv1:windowsSettings>
            <asmv1:longPathAware>true</asmv1:longPathAware>
        </asmv1:windowsSettings>
    </asmv1:application>
</assembly>

但是编译和启动应用程序会导致以下错误:

应用程序无法启动,因为它的并行配置不正确。请参阅应用事件日志或使用命令行 sxstrace.exe 工具获取更多详细信息。

使用 sxstrace.exe 可以查看:

INFO: Parsing Manifest File C:\test\longPaths.exe.
        INFO: Manifest Definition Identity is my.test.app,type="win32",version="1.0.0.0".
        ERROR: Line 2: The element ws1:longPathAware appears as a child of element urn:schemas-microsoft-com:asm.v1^windowsSettings which is not supported by this version of Windows.
ERROR: Activation Context generation failed.

也许

程序集的子元素也必须在此命名空间中

这并不完全正确(或者我解释错了)。尝试使用longPathAware元素中的完整示例:

<assembly xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv1:assemblyIdentity type='win32' name='my.test.app' version='1.0.0.0' />
    <asmv3:application>
        <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
            <ws2:longPathAware>true</ws2:longPathAware>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>

成功运行应用程序,但没有长路径意识(Windows错误代码206 = ERROR_FILENAME_EXCED_RANGE):

Exe SetCurrentDirectory failed 1 - 206
Exe SetCurrentDirectory failed 2 - 206

我检查了最终嵌入的资源,它肯定在那里: enter image description here

结束

我只能说我不知道还有什么其他测试,或者是否可以将longPathAware element添加到清单中以实现我正在尝试实现的应用程序类型。

也许有另一个api可以更改我的应用程序的当前工作文件夹为长路径,我会满意的,但至少_chdirstd::filesystem::current_path有相同的限制。

解决方法

使用短名称,即8.3别名,可能提供有限的解决方法。

对于我的案例,这通常是不可行的,因为短路径并不需要存在;它们可以在整个系统或卷上进行控制:
- 可以使用fsutil 8dot3name query查询一般状态。 - 可以使用fsutil behavior query disable8dot3 c:查询每个卷的设置。
附注:
- 清单可以嵌入可执行文件或dll中。 - 当包含它的dll被延迟加载时,它将被忽略。 - 当包含它的可执行文件时,延迟加载的dll不会忽略它。 - 因为清单在项目设置中是“附加项”,所以不需要assemblyIdentity元素

过去当我需要长路径时,我使用了UNC路径“\?\C:...”,但是很长时间没有研究过在Win10中是否需要或操作系统的更改。我想我是在Windows XP中这样做的。 - drescherjm
你是否设置了注册表策略以启用长路径? - Anders
@drescherjm 它们可适用于许多 API,但对 SetCurrentDirectory 不起作用 :( - ridilculous
@Anders,没有。我认为使用清单文件可以按应用程序选择加入,避免更改整个系统。清单文件中的设置是否与注册表设置绑定? - ridilculous
1个回答

3

清单适用于您的应用程序,它允许您选择长路径支持。

但是,长路径支持还必须在系统范围内启用。这是组策略“计算机配置 > 管理模板 > 系统 > 文件系统 > 启用Win32长路径”。

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
"LongPathsEnabled"=dword:00000001

这个设计没有任何意义,但它就是这样。你不能说它是为了兼容性而存在,因为至少从Windows 2000开始,人们可以使用\\?\来创建长路径。

啊,我明白了,它在这里描述:(https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation)。 我同意,这似乎没有意义。 不幸的是,我不能强制客户设置那个键。 我已经成功地为其他API使用了神奇前缀\?\和\?\UNC\,但它们似乎对SetCurrentDirectory无效,而这个功能对我正在开发的应用程序非常重要。 - ridilculous
我刚刚测试了一下,SetCurrentDirectory似乎只能通过注册表键和清单元素的组合来接受长路径;魔术前缀对它没有任何影响。这是困扰我的问题,因为对于所有其他应用程序,至少有一个可以使用魔术前缀的API来完成工作,即使有时这意味着我必须将字符串转换为wchar;当前目录似乎是个例外,我不明白为什么除了他们不想因某些原因触及其进程全局内存布局。 - ridilculous
1
我猜测为什么SetCurrentDirectory不接受'\?'的原因是,在旧世界中,PEB中的某些内容具有固定大小的缓冲区或类似的东西(也许与相对路径的A到W转换有关?),而其他路径API只是将字符串传递给内核。 - Anders
这也是我想的,但现在几乎可以确定没有绕过注册表的方法了,所以我进行了搜索,看起来它是动态的:RTL_USER_PROCESS_PARAMETERS。在这篇不错的博客文章中找到了该参考文献。 - ridilculous
1
是的,但我怀疑它与 A 函数在 TEB 中使用的 WCHAR StaticUnicodeBuffer[261] 有关。虽然这只是我的猜测,但显然在当前目录处理中存在一个固定缓冲区,可以防止这种情况发生25年前。 - Anders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接