\ ?? \和\\?\路径之间有区别吗?

Ily*_*lya 6 winapi internals

MSDN文档命名文件,路径和命名空间讨论\\?\前缀.报价:

对于文件I/O,路径字符串的"\?\"前缀告诉Windows API禁用所有字符串解析并将其后面的字符串直接发送到文件系统.

实验表明,\??\前缀具有相同的效果,禁用路径解析(..处理)和启用路径的时间长于MAX_PATH.

MSDN \\?称为"Win32文件命名空间",它是纯粹由Win32用户模式API知道并转换为\??NT命名空间吗?无论如何,通过Winobj,我GLOBAL??在NT命名空间中看到,而不是??.

Jon*_*ter 11

您的问题的答案是,是的,传递\\?\\??\用户模式功能之间存在差异.

在内部,NT始终表示带有\??\前缀的路径.通常,当您CreateDirectoryW使用普通路径调用用户模式函数(例如)时C:\foo,用户模式函数会调用一个名为的内部函数RtlDosPathNameToNtPathName_U,该函数将其转换为前缀为NT的NT样式路径\??\.这种转换是使用固定大小的静态缓冲区完成的,这是着名的MAX_PATH限制来源.

当你调用一个用户模式功能指定\\?\前缀(注意,只有一个?),RtlDosPathNameToNtPathName_U不是叫.相反,第二个反斜杠变成了?字符和路径是逐字使用的.当他们谈论\\?\关闭"...自动扩展路径字符串" 时,这就是文档的意思.

但是,当您使用\??\前缀调用用户模式函数时,该前缀记住是内部NT前缀,此扩展仍然完成.

用户模式功能专门用于\\?\禁用自动扩展过程,并且由于您没有提供它,因此您的路径被视为非前缀路径并被馈送到RtlDosPathNameToNtPathName_U.此功能足够智能,不会\??\在路径的开头添加额外的前缀,但仍使用固定大小的静态缓冲区.

这是关键的区别.当您\??\作为前缀传递时,您的路径仍受MAX_PATH长度限制.

以下示例程序演示了这一点.该TestNestedDir函数只是尝试创建(然后删除)MAX_PATH长度大于字符的路径,一次一个级别.如果运行此代码,您将看到的结果是:

CreateDir, no prefix = 0
CreateDir, prefix \\?\ = 1
CreateDir, prefix \??\ = 0
Run Code Online (Sandbox Code Playgroud)

只有使用\\?\前缀完成的创建才会成功.

#include <stdio.h>
#include <tchar.h>
#include <string>
#include <assert.h>
#include <Windows.h>

const wchar_t* pszLongPath = 
    L"C:\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890";

bool TestCreateNestedDir(LPCWSTR pszPath)
{
    std::wstring strPath = pszPath;
    std::wstring::size_type pos = 0, first = std::wstring::npos;
    bool fDirs = false, fResult = false;

    // step through each level in the path, but only try to start creating directories
    // after seeing a : character
    while ((pos = strPath.find_first_of(L'\\', pos)) != std::wstring::npos)
    {
        if (fDirs)
        {
            // get a substring for this level of the path
            std::wstring strSub = strPath.substr(0, pos);

            // check if the level already exists for some reason
            DWORD dwAttr = ::GetFileAttributesW(strSub.c_str());
            if (dwAttr != -1 && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
            {
                ++pos;
                continue;
            }

            // try to make the dir. if it exists, remember the first one we successfully made for later cleanup
            if (!::CreateDirectoryW(strSub.c_str(), nullptr))
                break;
            if (first == std::wstring::npos) first = pos;
        }
        else
        if (pos > 0 && strPath[pos - 1] == L':')
            fDirs = true;

        ++pos;
    }

    if (pos == std::wstring::npos)
    {
        // try to create the last level of the path (we assume this one doesn't exist)
        if (::CreateDirectoryW(pszPath, nullptr))
        {
            fResult = true;
            ::RemoveDirectoryW(pszPath);
        }
    }
    else
        --pos;

    // now delete any dirs we successfully made
    while ((pos = strPath.find_last_of(L'\\', pos)) != std::wstring::npos)
    {
        ::RemoveDirectoryW(strPath.substr(0, pos).c_str());
        if (pos == first) break;
        --pos;
    }
    return fResult;
}


int _tmain(int argc, _TCHAR* argv[])
{
    assert(wcslen(pszLongPath) > MAX_PATH);

    printf("CreateDir, no prefix = %ld\n", TestCreateNestedDir(pszLongPath));

    std::wstring strPrefix = L"\\\\?\\" + std::wstring(pszLongPath);
    printf("CreateDir, prefix \\\\?\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));

    strPrefix[1] = L'?';
    printf("CreateDir, prefix \\??\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)