The answer to your question is, yes there is a difference between passing \\?\
and \??\
to user mode functions.
Internally, NT always represents paths with the \??\
prefix. Normally, when you call a user mode function (e.g. CreateDirectoryW
) with a normal path like C:\foo
, the user mode functions call an internal function named RtlDosPathNameToNtPathName_U
which converts this to an NT-style path prefixed with \??\
. This conversion is done with a fixed size static buffer, which is where the famous MAX_PATH
limitation comes from.
When you call a user mode function specifying the \\?\
prefix (note, only one ?
), RtlDosPathNameToNtPathName_U
is not called. Instead, the second back-slash is turned into a ? character and the path is used verbatim. This is what the docs mean when they talk about \\?\
turning off the "...automatic expansion of the path string."
However, when you call a user mode function with the \??\
prefix, which remember is an internal NT prefix, this expansion is still done.
The user mode functions specifically look for \\?\
to disable the automatic expansion process, and since you're not providing it, your path is treated as a non-prefixed path and fed to RtlDosPathNameToNtPathName_U
. This function is smart enough not to add an extra \??\
prefix to the start of the path, however the fixed size static buffer is still used.
This is the key difference. When you pass \??\
as a prefix your paths are still subject to the MAX_PATH
length limit.
The following example program demonstrates this. The TestNestedDir
function simply attempts to create (and then delete) a path greater than MAX_PATH
characters in length, one level at a time. The results you'll see if you run this code are:
CreateDir, no prefix = 0
CreateDir, prefix \\?\ = 1
CreateDir, prefix \??\ = 0
Only the creation done with the \\?\
prefix is successful.
#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;
}