GetFileInformationByHandleEx を FileIdBothDirectoryInfo を指定して呼んだ時の処理がなかなか無かったので書いてみた。
メリット
・ファイル列挙の手段としては FindFirst/NextFile よりも速いらしい。というか、FindFirst/NextFile の内部動作っすねー。
・親ディレクトリのファイルハンドルが必要。
・ファイルの属性などにかかわらず全ファイルが列挙される。
デメリット
・ファイルの属性などにかかわらず全ファイルが列挙される。
・ワイルドカードの処理がないので一つのファイルの情報を得たい場合にはFindFirst/NextFileのほうが楽。
CAtlFile hFile; //ディレクトリのファイルハンドル。
hFile.Create(strPath, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS);
//ディレクトリをファイルとして開く場合には FILE_FLAG_BACKUP_SEMANTICS が必要なことに注意。
//const int FILENAME_LENGTH_MAX = 255;
//BYTE data[sizeof(FILE_ID_BOTH_DIR_INFO) + sizeof(WCHAR) * FILENAME_LENGTH_MAX];
BYTE data[64 * 1024];
DWORD dwErr;
FILE_ID_BOTH_DIR_INFO *pfi = NULL;
for (; ;) {
if (pfi == NULL || pfi->NextEntryOffset == 0) {
SetLastError(ERROR_SUCCESS);
memset(data, 0, sizeof(data));
rc = GetFileInformationByHandleEx(hFile, FileIdBothDirectoryInfo, data, _countof(data));
if (rc == FALSE) {
break;
}
pfi = (FILE_ID_BOTH_DIR_INFO *)data;
}
else {
pfi = (FILE_ID_BOTH_DIR_INFO *)(((BYTE *)pfi) + pfi->NextEntryOffset);
}
CStringW cFileName = CStringW(pfi->FileName, pfi->FileNameLength / sizeof(WCHAR));
CStringW strChild = strPath + L"\\" + cFileName;
// ...
}
dwErr = GetLastError();
if (dwErr == (pfi == NULL ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES)) {
// まだファイルを一度も見つけられずにループを場合には ERROR_FILE_NOT_FOUND、
// ひとつ以上のファイルを見つけてループを抜けた場合には ERROR_NO_MORE_FILES、
// がエラー値として返るので、この場合はエラーではないとする。
// (ERROR_FILE_NOT_FOUND になることはありえない気がするけれど?) → 気にしない
dwErr = ERROR_SUCCESS;
}
FileIdBothDirectoryInfo で GetFileInformationByHandleEx すると、渡されたメモリ領域に入るだけファイル情報が詰め込まれる。
詰め込まれる個数は列挙されるファイル名の長さによって不定。
一つもファイル情報を格納できないバッファを渡すとかしないよう、
バッファサイズは sizeof(FILE_ID_BOTH_DIR_INFO) + sizeof(WCHAR) * (FILENAME_LENGTH_MAX - 1) 以上確保したほうが良い。NTFS だと FILENAME_LENGTH_MAX=255。
同じファイルハンドルに対して、再び GetFileInformationByHandleEx(FileIdBothDirectoryInfo) すると、前回の続きのファイルが取得できる。
これを最初に戻したかったら、引数をFileIdBothDirectoryRestartInfo にして呼んでやる。