如何启用 "Long Path Aware" 行为以在 C++ windows 控制台应用程序中设置当前目录

How to enable "Long Path Aware" behavior for setting the current directory in a C++ windows console app

在 windows 上的 C++ 控制台应用程序中,我试图破坏 MAX_PATH restriction for the SetCurrentDirectoryW 函数。

已经提出了许多类似的问题,但 none 得到了可用的答案:

文档研究

显然这可以通过使用 application manifest files. The docs for SetCurrentDirectoryW state:

Tip Starting with Windows 10, version 1607, for the unicode version of this function (SetCurrentDirectoryW), you can opt-in to remove the MAX_PATH limitation. See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.

并且来自 the general docs about Manifests

Manifests are XML files that accompany and describe side-by-side assemblies or isolated applications. ... Application Manifests describe isolated applications. They are used to manage the names and versions of shared side-by-side assemblies that the application should bind to at run time. Application manifests are copied into the same folder as the application executable file or included as a resource in the application's executable file.

docs about Assembly Manifests再次指出与Application Manifests的区别:

As a resource in a DLL, the assembly is available for the private use of the DLL. An assembly manifest cannot be included as a resource in an EXE. An EXE file may include an Application Manifests as a resource.

所需的docs about Application Manifests list the assembly and assemblyIdentity个元素:

所有其他元素和属性似乎都是可选的。

assembly element 的其他要求是:

Its first subelement must be a noInherit or assemblyIdentity element. The assembly element must be in the namespace "urn:schemas-microsoft-com:asm.v1". Child elements of the assembly must also be in this namespace, by inheritance or by tagging.

最后,longPathAware element which is optional but which should hopefully allow SetCurrentDirectoryW 使用长路径:

Enables long paths that exceed MAX_PATH in length. This element is supported in Windows 10, version 1607, and later. For more information, see 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>

它似乎不完全遵循 assembly element 中的规则:

The assembly element must be in the namespace "urn:schemas-microsoft-com:asm.v1". Child elements of the assembly must also be in this namespace, by inheritance or by tagging.

测试

测试环境为:

测试应用程序是一个新的 C++ 控制台应用程序,我对 Additional Manifest Files 进行了以下更改:

源代码非常简单;它也在 godbolt 但没有清单:

#include <iostream>
#include <string>
#include <windows.h> 

int main() {
    std::wstring const path = LR"(H:\test\longPaths\manySmallLongPaths345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678345678\)";
    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());
    }
}

尝试将所有这些放在一起,我认为以下文件可能是有效的 Application Manifest:

<?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>

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

The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.

使用 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.

可能

Child elements of the assembly must also be in this namespace

不完全正确(不再)或者我解释错了。尝试使用 longPathAware element:

中的完整示例
<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

我检查了最终的嵌入资源,但它确实存在:

完成

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

也许还有另一个 api 可以将我的应用程序的当前工作文件夹更改为一个长路径,我会接受它,但至少 _chdir and std::filesystem::current_path 具有相同的限制。

解决方法

又名使用简称。 8.3 别名可能提供有限的解决方法。

对于我的情况,这通常是不可行的,因为不需要存在短路径;它们可以在系统范围内或按卷进行控制:

旁注

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

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

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

这种设计毫无意义,但事实就是如此。您不能以兼容性的名义争论它,因为至少从 Windows 2000 年开始,就可以使用 \?\ 创建长路径。