Win32API

CreateFileW等のwin32apiでMAX_PATH 超のメモ

More than 1 year has passed since last update.

MAX_PATH 超をたまに目にして気になっているけどそのままに放置していたので個人メモのまとめ。


はじめに

Win32 の関数は、多くはMAX_PATH (260)制限がある。

実は一部APIはそれ以上を渡す機能がある。

32767 文字まで可能。extended length path というらしい。

Naming Files, Paths, and Namespaces

ここに書いてあることによれば、Ntfs は "\\?\" プレフィクスを付けると、長いファイル名が利用できる。また、それ以下のパス部分を変換無しで直接扱う。(\と/の変換が行われなくなるらしい)


サポートされている関数(一部

ファイルパスを受け取るもので、ユニコード版(Wで終わる方)のものが一部サポートしている。

ANSI版(Aで終わる方)はMAX_PATHまで。

とりあえずmsdnを探って自分が使いそうなものだけ。

ファイル名を渡す系のapiを使うときは一度msdnの該当関数を見た方がよさそう。


FindFirstFile 等の注記

WIN32_FIND_DATA は AもWも MAX_PATH を使っている。

FineFirstFile/FindNextFile 関数も、指定フォルダ下のファイル名部分を返す。

exteneded length path でも、バックスラッシュで区切ってある間に上限はある(File Component nameと呼ぶらしい)

そこが255なので、実際にはMAX_PATH でも extended lenght path を利用する分に問題はない。

一応 255 の部分も GetVolumeInformation 関数 でちゃんと確認しておいた方がよさそう。


Shlwapi のこと

ファイル名の取り扱いなどの便利ライブラリがあったのだけれども、基本MAX_PATHまでだった。shlwapiの関数

Win8以降だと、PathCchXxx な関数Shell Functionsが追加されており、以下のプレフィクスを持つパスも扱えるようになっている。

* '\\'

* "\\?\"

* "\\?\UNC\"

Win8以降さえなかったら。


実験コード

\\?\ を付加して実際にMAX_PATHより長いファイル/ディレクトリを扱ってみる。

とりあえず呼び出しているだけです。

os win10 x64, vs2015, x86-build


c++

// todo: _WIN32_WINNT は何を指定する?

#include <windows.h>
#include <tchar.h>

#include <string>
#include <iostream>

const wchar_t TEST_ROOT[] = LR"(\\?\C:\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccde)";

int main()
{
DWORD compLen = 0;
if (!GetVolumeInformationW(LR"(C:\)", nullptr, 0, nullptr, &compLen, nullptr, nullptr, 0))
{
std::wcerr<<L"cant get volume info for C drive"<<std::endl;
return -1;
}
std::wcout<<"a";

if (compLen!=255)
{
std::wcerr<<LR"(\\?\プレフィクスが扱えない。(気がする))"<<std::endl;
return -2;
}
std::wcout<<"b";

// MAX_PATHなフォルダを作る。
auto dir_sts1 = ::GetFileAttributesW(TEST_ROOT);
if (dir_sts1==INVALID_FILE_ATTRIBUTES||!(dir_sts1&FILE_ATTRIBUTE_DIRECTORY))
{
if (!::CreateDirectoryW(TEST_ROOT, nullptr))
{
std::wcerr<<L"create dir1 error"<<(int)::GetLastError()<<std::endl;
return -1;
}
}
std::wcout<<"c";
// MAX_PATH より長い文字で子dir を作る。
std::wstring dir;
dir = TEST_ROOT;
dir.append(L"\\");
dir.append(L"child-dir");
auto dir_sts = ::GetFileAttributesW(dir.c_str());
if (dir_sts==INVALID_FILE_ATTRIBUTES||!(dir_sts&FILE_ATTRIBUTE_DIRECTORY))
{
if (!::CreateDirectoryW(dir.c_str(), nullptr))
{
std::wcerr<<L"create dir error"<<(int)::GetLastError()<<std::endl;
return -1;
}
}
std::wcout<<"d";
// さらにファイルハンドルを作成。
std::wstring file;
file = dir;
file.append(L"\\");
file.append(L"testfile.txt");
auto hFile = ::CreateFileW(file.c_str(), FILE_GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile==INVALID_HANDLE_VALUE)
{
std::wcerr<<L"create file error"<<(int)::GetLastError()<<std::endl;
return -1;
}
std::wcout<<"e";

// 書きこみなどは handle 経由なので気にしない。
if (!::WriteFile(hFile, L"aaaaaaaaaa", sizeof(L"aaaaaaaaaa"), nullptr, nullptr))
{
std::wcerr<<L"write file error"<<(int)::GetLastError()<<std::endl;
::CloseHandle(hFile);
return -1;
}
std::wcout<<"f";

::CloseHandle(hFile);

// 結果を FindFirstFileW 経由で列挙。
WIN32_FIND_DATAW dat;
std::wstring search;
search = dir;
search.append(L"\\");
search.append(L"*");
HANDLE hFind = FindFirstFileW(search.c_str(), &dat);
if (hFind==INVALID_HANDLE_VALUE)
{
std::wcerr<<L"Cant find file error"<<(int)::GetLastError()<<std::endl;
return -1;
}
std::wcout<<"g";

do
{
// FindFile された結果にその属性を取得して表示。
// TODO: ここで ".", ".." が属性 -1 になっている理由の確認。
// \\?\ に ../ 系の記述をしたから?それとももともとそういう値がもどる?
std::wstring tmp;
tmp = dir;
tmp.append(L"\\");
tmp.append(dat.cFileName);
auto attr = ::GetFileAttributesW(tmp.c_str());
std::wcout<<L"name: "<<dat.cFileName<<L" "<<attr<<std::endl;
} while (FindNextFileW(hFind, &dat));
FindClose(hFind);
std::wcout<<"h";

// さっき作った実験ファイルを削除。
if (!::DeleteFileW(file.c_str()))
{
std::wcerr<<L"Cant delete file error"<<(int)::GetLastError()<<std::endl;
return -1;
}
std::wcout<<"i";

// 実験Dirも削除。
if (!::RemoveDirectoryW(dir.c_str()))
{
std::wcerr<<L" dir error"<<(int)::GetLastError()<<std::endl;
return -1;
}
std::wcout<<"j";
if (!::RemoveDirectoryW(TEST_ROOT))
{
std::wcerr<<L" dir1 error"<<(int)::GetLastError()<<std::endl;
return -1;
}
std::wcout<<"k";
return 0;
}



その他


MAX_PATH

MAX_PATH のサイズには、最後のnul文字も含まれている。(けどwin32に MAX_PATH+1を要求してくる関数があったような


.net framework のファイル名系の関数

referencesource を覗いた限り(2016/05/01閲覧)は、"\\", "\\?\", "\\?\UNC\" 用のコードは入っているので動きそう。

TODO: msdn等公式に明言されている箇所を探す。

dotnet 4.6.2 で Fix 260 character file name length limitation のSystem.IO APIに長いパスサポートが入った。

ということは今まで含まれていないということに。


エクスプローラー

Win10のエクスプローラーだと、MAX_PATH 以上のフォルダも開けるし、ファイルをopenする(普通にダブルクリックで開く)ことができたので案外動きそう。ただし、エクスプローラーからMAX_PATH 以上になるファイル/フォルダを作成しようとするとエラーになる。

[Window Title]

名前の変更

[Content]

指定されたファイル名は、無効かまたは長すぎます。

別の名前を指定してください。

[OK]

右クリックメニューの新規作成(新しいリッチ テキスト ドキュメント.rtfのようなファイル)では作られてしまったので、

できないというよりは、作らせないようにガードしているという印象。


GetModuleFileName でもらえるパス

DLL等がロードされたときの文字列がそのまま取得されるらしい。

だから、8.3形式が得られたり、\\?\ プレフィクス付きで得られたりする可能性がある。

GetModuleFileName function