如何在Windows上以编程方式检查当前进程是否支持长路径?

8

在Windows 10的1607版本中,进程可以通过使用清单属性(https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath)选择长路径感知。

我如何以编程方式检查调用进程是否具有长路径感知?请注意,仅检查操作系统版本或注册表键的值是不够的,因为存在Windows版本>= 1607、长路径在系统范围内被禁用并且进程未发出长路径信号的情况。


3
阅读文档,我认为其理论是你不需要知道。你能简述一下为什么你认为这可能会成为一个问题吗? - Harry Johnston
2
如果你正在提供一个库,我认为调用过程应该知道是否允许LFN。无论如何,这个功能似乎更像是噱头而不是其他什么,我怀疑任何生产软件都无法依赖它。 - Jonathan Potter
3
如果你之前没有感觉到需要警告用户关于路径长度的限制,我不明白为什么现在有这个必要。即使这个特定的库因为某种原因特别容易出现问题,合适的解决方案肯定是使用现有的长路径支持,这会为所有人解决问题。 - Harry Johnston
1
有趣的是,系统确实在注册表中查询“LongPathsEnabled”值并使用它(当首次调用“RtlAreLongPathsEnabled”时),但看起来系统没有使用清单文件。我创建了一个适当的应用程序,但LPN仍然被禁用。 - RbMm
2
实际上,没有理由通过编程来确定这一点。如果你正在编写该过程,那么你已经知道答案,因为你创建了清单。如果你正在编写一个库,你将使用传递给你的任何路径,并以兼容的方式管理你的内部路径操作(“\?\”),如果在未清单调用应用程序时传递了长路径,则会崩溃(就像你应该做的那样)。 - GSerg
显示剩余3条评论
2个回答

3
尽管文档称Win32和UWP应用程序都可以启用长路径名,但对于UWP而言,它是不可用的。API KernelBase!BasepIsProcessLongPathAwareByManifest使用SxS API从清单中获取值,但此API对于UWP无效。可以通过手动在PEB中设置正确的位来解决该问题:
NtCurrentTeb()->ProcessEnvironmentBlock->IsLongPathAwareProcess = 1;

TEB的定义可以从winternl.h中复制,IsLongPathAwareProcess位是第4个字节中最重要的位。也就是说,这可以被重写为:

最初的回答:在winternl.h中可以找到TEB的定义。IsLongPathAwareProcess位是第四个字节中最高位。简单来说,可以将其重写为:

((unsigned char*)NtCurrentTeb()->ProcessEnvironmentBlock)[3] |= 0x80;

2

ntdll(在Win10 1607中)导出下一个API BOOLEAN NTAPI RtlAreLongPathsEnabled(); - 因此您可以调用它。如果启用了长路径,则返回TRUE

这是代码片段 - 如果RtlAreLongPathsEnabled返回false,则会返回STATUS_NAME_TOO_LONG(c0000106)

enter image description here

系统需要在使用任何文件函数之前将Win32路径转换为NT路径,这是通过调用 RtlDosPathNameTo*NtPathName* 来完成的。如果这些函数发现路径超过了MAX_PATH(约为260个字符) - 将调用 RtlAreLongPathsEnabled() 并仅在该函数返回TRUE时继续工作,在返回false时会返回STATUS_NAME_TOO_LONGRtlAreLongPathsEnabled 的代码非常简单 - 第一次调用时,它只检查注册表并保存结果。完全不考虑清单。以下是该函数的代码:
BOOLEAN RtlAreLongPathsEnabled()
{
    static BOOLEAN init;
    static BOOLEAN elp;
    if (!init)
    {
        init = true;
        HANDLE hKey;
        KEY_VALUE_PARTIAL_INFORMATION kvpi;
        STATIC_OBJECT_ATTRIBUTES(FileSystemRegKeyName, "\\registry\\MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem");
        if (0 <= ZwOpenKey(&hKey, KEY_READ, &FileSystemRegKeyName))
        {
            STATIC_UNICODE_STRING(LongPathRegKeyValue, "LongPathsEnabled");
            if (0 <= ZwQueryValueKey(hKey, &LongPathRegKeyValue, KeyValuePartialInformation, &kvpi, sizeof(kvpi), &kvpi.TitleIndex) &&
                kvpi.Type == REG_DWORD && kvpi.DataLength == sizeof(DWORD))
            {
                elp = *(DWORD*)kvpi.Data != 0;
            }
            ZwClose(hKey);
        }
    }
    return elp;
}

因此我的结论是-在当前版本中,长路径的行为仅取决于注册表设置,并且绝对不依赖于应用程序清单,尽管MSDN上有所描述。

对于那些反对意见-我只是感兴趣-你们中是否有人构建了测试应用程序(有或没有清单)并自己测试过,还是只能阅读文档?

对于那些觉得难以理解或者太懒得编写代码的人,可以使用以下代码进行测试:

BOOL CreateFolder(LPCWSTR lpPathName)
{
    return CreateDirectoryW(lpPathName, 0) || GetLastError() == ERROR_ALREADY_EXISTS;
}

void LPT()
{
    WCHAR name[128], path[0x8000], *c;

    if (!SHGetFolderPath(0, CSIDL_PROFILE , 0, 0, path))
    {
        *name = '\\';
        __stosw((PUSHORT)name + 1, '3', RTL_NUMBER_OF(name) - 2);
        name[RTL_NUMBER_OF(name) - 1] = 0;

        c = path + wcslen(path);

        int n = 4;
        do 
        {
            memcpy(c, name, sizeof(name));
            c += RTL_NUMBER_OF(name) - 1;

            if (!CreateFolder(path))
            {
                break;
            }

        } while (--n);

        if (!n)
        {
            wcscpy(c, L"\\1.txt");

            HANDLE hFile = CreateFileW(path, FILE_GENERIC_WRITE, FILE_SHARE_VALID_FLAGS, 0, OPEN_ALWAYS, 0, 0);

            if (hFile != INVALID_HANDLE_VALUE)
            {
                CloseHandle(hFile);
                return ;
            }
        }
    }

    GetLastError();
}

使用清单中的<ws2:longPathAware>true</ws2:longPathAware>和注册表中的LongPathsEnabled==0进行测试。如果失败了,尝试在没有清单的情况下使用LongPathsEnabled==1的注册表进行测试。是否成功?

如果是这样,在Windows 10上进行测试。版本1607,构建14393.0。


在Win10 1709中,实现方式已经改变:现在RtlAreLongPathsEnabled非常简单。

enter image description here

BOOLEAN RtlAreLongPathsEnabled()
{
    return NtCurrentTeb()->ProcessEnvironmentBlock->IsLongPathAwareProcess;
}

在之前的构建中是这样的:

enter image description here


1
是的,我想要确定当前正在运行的进程是否启用了长路径,我可以看到MSDN中的清单片段对RtlAreLongPathsEnabled API调用的输出没有影响,也不会使CreateDirectoryW与我刚刚测试的266个字符路径一起工作。 - Jake Petroules
3
可能出现的负评是因为您的回答似乎是一种大胆尝试,试图通过使用未记录的代码来揭示和谴责邪恶的文档。问题在于这样做不仅不会使您看起来可信,而且还无法证明任何事情,因为您展示的代码路径并不被认为是唯一/相关的路径。 - GSerg
2
如果您删除答案的整个内容,并用表格替换它,其中您将得到使用程序中的长路径的结果(成功/失败),对于以下属性的每种可能组合,这将有助于每个人:应用程序类型(本机,.net,通用),架构(x86,x64),清单的存在,<ws2:longPathAware>设置为truefalse,以及注册表标志设置为10(共36种组合)。 - GSerg
3
根据我在v1709上的测试,长路径支持和RtlAreLongPathsEnabled函数的结果都取决于应用程序清单和注册表。如果在注册表中启用了长路径支持,但在清单中没有启用(或反之亦然),则长路径将无法正常工作,并且该函数将返回FALSE。 - Lexikos
3
@Lexikos- 是的,你是对的- 在1709中对实现进行了更改,与1607相比。 - RbMm
显示剩余19条评论

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