FindFirstFileとFindNextFileでディレクトリを列挙する
FindFirstFileとFindNextFileは、Windows上のディレクトリを列挙するための代表的なWin32 APIです。検索パスにワイルドカードを指定すると、最初の一件を取得し、以後は検索ハンドルを使って同じディレクトリの次のエントリへ進めます。ファイル名だけでなくWIN32_FIND_DATAの属性も取得できるため、ファイル一覧の表示や、不要な項目の除外に利用できます。
実装では、最初に取得した検索ハンドルを必ずFindCloseで解放することが重要です。また、列挙の正常終了と実際の失敗を区別するには、APIが失敗した直後にGetLastErrorを呼び出す必要があります。Windows APIを直接扱うため、Unicode版のFindFirstFileWとFindNextFileWを使うのが基本です。
基本的な呼び出しと選択基準
FindFirstFileWは、指定したパターンに一致する最初の項目を取得し、HANDLE型の検索ハンドルを返します。該当する項目がない場合や検索開始自体に失敗した場合はINVALID_HANDLE_VALUEになります。FindNextFileWはそのハンドルを使って次の項目を取得し、列挙の終端では偽を返します。
| 観点 | FindFirstFileWとFindNextFileW | FindFirstFileExWとFindNextFileW | std::filesystem::directory_iterator |
|---|---|---|---|
| 開始方法 | ワイルドカード付きのパスを指定 | 情報レベルと検索条件を指定 | ディレクトリパスをコンストラクターに渡す |
| 項目情報 | WIN32_FIND_DATAWへ格納 |
WIN32_FIND_DATAWへ格納 |
directory_entryとして取得 |
| 並び順 | 保証されない | 保証されない | 保証されない |
| 終了条件 | GetLastErrorがERROR_NO_MORE_FILES |
同左 | 終端イテレーターとの比較 |
| 主な特徴 | .win32 APIを直接制御できる | FindExInfoBasicなどを選択できる |
C++17で移植性の高い処理を書く |
Windows APIに固定した実装なら、最初の組は属性取得からエラー処理まで隠れにくいという利点があります。標準ライブラリと組み合わせる場合も、std::filesystem::pathによるパス構築をcurityします。Windows固有の仕様を横断して確認する用途には、API実装の参考資料の各カテゴリが役立ちます。
実装コードと処理の流れ
検索対象が直下の項目だけなら、ディレクトリパスに\*を付けます。WIN32_FIND_DATAWには、ファイル名、サイズ、タイムスタンプ、ファイル属性が格納されます。次の例では、FindFirstFileWで列挙を開始し、ディレクトリ以外の項目名を返します。
#include <windows.h>
#include <string>
#include <vector>
bool EnumerateFiles(const std::wstring& directory,
std::vector<std::wstring>& files) {
std::wstring pattern = directory;
if (!pattern.empty() && pattern.back() != L'\\') {
pattern.push_back(L'\\');
}
pattern.push_back(L'*');
WIN32_FIND_DATAW data{};
HANDLE handle = FindFirstFileW(pattern.c_str(), &data);
if (handle == INVALID_HANDLE_VALUE) {
const DWORD error = GetLastError();
return error == ERROR_FILE_NOT_FOUND;
}
do {
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
files.emplace_back(data.cFileName);
}
} while (FindNextFileW(handle, &data));
const DWORD error = GetLastError();
FindClose(handle);
return error == ERROR_NO_MORE_FILES;
}
このコードで得られるのはcFileNameだけなので、完全なパスが必要ならディレクトリ側と結合します。また、FindFirstFileWは隠しファイルやシステムファイルも返すため、属性による絞り込みは別に行います。FindNextFileWが偽を返したら、直ちにGetLastErrorを取得してからFindCloseを呼び出す順序を守ってください。
異常終了とエラー処理
FindFirstFileWがINVALID_HANDLE_VALUEを返した場合、ERROR_FILE_NOT_FOUNDは空のディレクトリや一致する項目がない状況として扱う場合があります。一方、ERROR_PATH_NOT_FOUNDはパス自体が存在しない可能性、ERROR_ACCESS_DENIEDはアクセス権不足を示します。ディレクトリが空である場合と検索が失敗した場合を、同じ結果として扱わないことが重要です。
FindNextFileWの失敗では、ERROR_NO_MORE_FILESなら通常の列挙終了です。それ以外のエラーコード이라면、処理途中の失敗として報告します。検索ディレクトリがネットワーク上にある場合は応答に時間がかかることがあるため、列挙処理をワーカースレッドへ移し、フレームワークの推奨方式でUIの状態だけを更新すると無応答に見えにくくなります。処理中のカーソル表示のような状態表示を組み合わせれば、実行中であることを利用者へ示せます。
実運用で守るべき項目
ディレクトリ列挙を安定させるには、APIの呼び出しだけでなく、パス、属性、終了条件、ハンドルの寿命をまとめて管理する必要があります。
- 文字列には
W版のAPIを選び、std::wstringとwchar_tを組み合わせてANSI変換の事故を避けます。 - 検索パターンは必要なディレクトリ直下だけを指すようにし、意図しない範囲の走査を防ぎます。
FILE_ATTRIBUTE_DIRECTORY、FILE_ATTRIBUTE_HIDDEN、FILE_ATTRIBUTE_SYSTEM、FILE_ATTRIBUTE_REPARSE_POINTを用途に応じて確認します。FindNextFileWが失敗した直後にGetLastErrorを取得し、ERROR_NO_MORE_FILESとその他のエラーを区別します。FindCloseは例外や途中終了でも実行できるよう、RAIIやスコープガードで確実な解放を保証します。- 完全なパスの生成には
std::filesystem::pathを利用し、Windowsの長いパス対応とマニフェストの設定も確認します。 - 表示順を固定したい場合は、収集後にファイル名を separately ソートし、ファイルシステム側の列挙順に依存しないようにします。
列挙結果を画面へ反映する処理は、worker thread的结果をメッセージやPostMessageで通知する構成が適しています。時刻を確認しながら状態を更新する必要がある場合は、タイマーとイベント処理の設計を組み合わせると、スレッド間の同期と定期実行を整理できます。
検索ハンドルを保持している間にファイルが変更されても、一覧がトランザクションとして固定されるわけではありません。列挙結果を保存した後は、削除や移動の直前に 대상ファイルの存在と属性を再確認してください。-collections後にanother-processが同名のファイルを作成的情况下もあるため、必要なら.Attributesや更新時刻もを使って対象を識別します。
覚えておきたい設計原則
このAPI群の中心は、検索ハンドルで列挙位置を管理し、項目を一件ずつ取得する点上にあります。FindFirstFileWで開始し、FindNextFileWで次へ進み、GetLastErrorで終了理由を確認し、最後にFindCloseでリソースを解放する流れが基本です。ディレクトリやサブディレクトリを区別Wdthには、WIN32_FIND_DATAWの属性を使います。
記憶しておくべき要点は、正常終了とエラー終了をGetLastErrorで明示的に区別することと、検索ハンドルを必ず解放することです。この順序と属性判定を確実に行えば、FindFirstFileとFindNextFileによるWindowsのディレクトリ列挙を、安定した処理として実装できます。