hozugawa.net · A neutral informational page

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の呼び出しだけでなく、パス、属性、終了条件、ハンドルの寿命をまとめて管理する必要があります。

列挙結果を画面へ反映する処理は、worker thread的结果をメッセージやPostMessageで通知する構成が適しています。時刻を確認しながら状態を更新する必要がある場合は、タイマーとイベント処理の設計を組み合わせると、スレッド間の同期と定期実行を整理できます。

検索ハンドルを保持している間にファイルが変更されても、一覧がトランザクションとして固定されるわけではありません。列挙結果を保存した後は、削除や移動の直前に 대상ファイルの存在と属性を再確認してください。-collections後にanother-processが同名のファイルを作成的情况下もあるため、必要なら.Attributesや更新時刻もを使って対象を識別します。

覚えておきたい設計原則

このAPI群の中心は、検索ハンドルで列挙位置を管理し、項目を一件ずつ取得する点上にあります。FindFirstFileWで開始し、FindNextFileWで次へ進み、GetLastErrorで終了理由を確認し、最後にFindCloseでリソースを解放する流れが基本です。ディレクトリやサブディレクトリを区別Wdthには、WIN32_FIND_DATAWの属性を使います。

記憶しておくべき要点は、正常終了とエラー終了をGetLastErrorで明示的に区別することと、検索ハンドルを必ず解放することです。この順序と属性判定を確実に行えば、FindFirstFileとFindNextFileによるWindowsのディレクトリ列挙を、安定した処理として実装できます。