如何启用 "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 得到了可用的答案:
- How to enable "Long Path Aware" behavior via manifest in a C++ executable?
- Are long path behavior per app can be enable via the manifest?
文档研究
显然这可以通过使用 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 只需要一个属性:
- 清单版本
- manifestVersion 属性必须设置为 1.0。
assemblyIdentity element需要以下属性:
- 类型
- 取值必须为Win32且全部小写
- 姓名
- 名称使用以下格式:Organization.Division.Name。例如 Microsoft.Windows.mysampleApp.
- 版本
- 指定应用程序或程序集版本。使用四部分版本格式:mmmmm.nnnnn.ooooo.ppppp。由句点分隔的每个部分可以是 0-65535(含)。有关详细信息,请参阅 Assembly Versions。
所有其他元素和属性似乎都是可选的。
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.
测试
测试环境为:
- 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\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 别名可能提供有限的解决方法。
对于我的情况,这通常是不可行的,因为不需要存在短路径;它们可以在系统范围内或按卷进行控制:
- 一般状态可以用
fsutil 8dot3name query
查询
- 可以使用
fsutil behavior query disable8dot3 c:
查询每个卷设置
旁注
- 清单可以嵌入到可执行文件或 dll 中。
- 当包含它的dll为delay loaded时将被忽略。
- 当可执行文件包含它时,它不会被延迟加载的dll忽略。
- 因为Manifest在项目设置中是“附加的”,所以不需要assemblyIdentity element。
清单适用于您的应用程序,它允许您选择加入长路径支持。
但是,还必须在系统范围内启用长路径支持。这是组策略“计算机配置 > 管理模板 > 系统 > 文件系统 > 启用 Win32 长路径”。
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
"LongPathsEnabled"=dword:00000001
这种设计毫无意义,但事实就是如此。您不能以兼容性的名义争论它,因为至少从 Windows 2000 年开始,就可以使用 \?\
创建长路径。
在 windows 上的 C++ 控制台应用程序中,我试图破坏 MAX_PATH restriction for the SetCurrentDirectoryW 函数。
已经提出了许多类似的问题,但 none 得到了可用的答案:
- How to enable "Long Path Aware" behavior via manifest in a C++ executable?
- Are long path behavior per app can be enable via the manifest?
文档研究
显然这可以通过使用 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 只需要一个属性:
- 清单版本
- manifestVersion 属性必须设置为 1.0。
- 清单版本
assemblyIdentity element需要以下属性:
- 类型
- 取值必须为Win32且全部小写
- 姓名
- 名称使用以下格式:Organization.Division.Name。例如 Microsoft.Windows.mysampleApp.
- 版本
- 指定应用程序或程序集版本。使用四部分版本格式:mmmmm.nnnnn.ooooo.ppppp。由句点分隔的每个部分可以是 0-65535(含)。有关详细信息,请参阅 Assembly Versions。
- 类型
所有其他元素和属性似乎都是可选的。
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.
测试
测试环境为:
- 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\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 别名可能提供有限的解决方法。
对于我的情况,这通常是不可行的,因为不需要存在短路径;它们可以在系统范围内或按卷进行控制:
- 一般状态可以用
fsutil 8dot3name query
查询
- 可以使用
fsutil behavior query disable8dot3 c:
查询每个卷设置
旁注
- 清单可以嵌入到可执行文件或 dll 中。
- 当包含它的dll为delay loaded时将被忽略。
- 当可执行文件包含它时,它不会被延迟加载的dll忽略。
- 因为Manifest在项目设置中是“附加的”,所以不需要assemblyIdentity element。
清单适用于您的应用程序,它允许您选择加入长路径支持。
但是,还必须在系统范围内启用长路径支持。这是组策略“计算机配置 > 管理模板 > 系统 > 文件系统 > 启用 Win32 长路径”。
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
"LongPathsEnabled"=dword:00000001
这种设计毫无意义,但事实就是如此。您不能以兼容性的名义争论它,因为至少从 Windows 2000 年开始,就可以使用 \?\
创建长路径。