DeleteFileとRemoveDirectoryでファイル・フォルダを削除する
Windows APIでファイルやフォルダを削除する処理は、Win32プログラムで頻繁に登場します。ファイルにはDeleteFile、ディレクトリにはRemoveDirectoryを使いますが、対象の種類を間違えると失敗するため、両者の役割を明確に分けることが重要です。
削除処理は呼び出し自体が簡単でも、相対パス、アクセス権、読み取り専用属性、ファイルの使用中といった条件に左右されます。戻り値を確認し、失敗時にGetLastErrorで原因を取得する設計にすると、原因調査や利用者への通知がしやすくなります。
DeleteFileでファイルを削除する
DeleteFileは、指定したファイルを削除するWindows APIです。C++ではUnicode対応を優先してDeleteFileWを使うか、DeleteFileがUnicode版へ展開される環境でTCHAR系の文字列を使用します。
#include <windows.h>
bool DeleteTargetFile(const wchar_t* path)
{
if (DeleteFileW(path)) {
return true;
}
DWORD error = GetLastError();
// errorをログへ記録する
return false;
}
成功時の戻り値はTRUE、失敗時はFALSEです。対象ファイルが存在しない場合、別のプロセスが開いている場合、削除権限がない場合などで失敗します。削除に失敗したときは、単に「削除できません」と表示するのではなく、エラーコードを記録しておくと運用上の手掛かりになります。
RemoveDirectoryでフォルダを削除する
RemoveDirectoryはディレクトリそのものを削除します。ただし、対象フォルダにファイルやサブフォルダが残っている場合は削除できません。中身を消してから最後に親ディレクトリを削除する、という順序が基本です。
bool RemoveEmptyFolder(const wchar_t* path)
{
if (RemoveDirectoryW(path)) {
return true;
}
DWORD error = GetLastError();
// errorをログへ記録する
return false;
}
このAPIは再帰的な削除を行わないため、階層構造をまとめて消す処理では、FindFirstFileやFindNextFileで内容を列挙し、ファイルをDeleteFileWで削除します。その後、子ディレクトリから親ディレクトリの順にRemoveDirectoryWを呼び出します。
パスと文字コードを正しく扱う
Windows APIへ渡すパスは、処理対象を正確に指していなければなりません。相対パスはカレントディレクトリの影響を受けるため、サービスやショートカットから起動したプログラムでは、想定外の場所を参照することがあります。可能なら絶対パスへ変換してから削除します。
日本語や長いパスを扱う場合は、ワイド文字版のDeleteFileWとRemoveDirectoryWを選ぶのが安全です。std::wstringやL"..."形式の文字列を使い、ANSI版とUnicode版を処理の途中で混在させないようにします。
削除対象を画面で選択させる構成では、ファイルパスだけでなく、入力ダイアログやコントロールの扱いも関係します。Win32のモーダル画面を組み込む場合は、ダイアログの作成に関する実装例も参考になります。
削除前に確認する項目
利用者のデータを扱う処理では、APIを呼ぶ前に対象の状態を確認します。確認処理と削除処理の間に対象が変化する可能性は残りますが、明らかな誤操作や不適切な対象を減らす効果があります。
- 指定パスがファイルかディレクトリか
- パスが空文字列や意図しない相対パスではないか
- 対象がアプリケーションの管理範囲に含まれているか
- 読み取り専用属性や隠し属性が設定されていないか
ファイルの存在確認にはGetFileAttributesWなどを利用できます。ただし、存在確認が成功したからといって、その後の削除まで必ず成功するとは限りません。状態確認は補助的な検証と考え、最終的には削除APIの戻り値を必ず判定します。
失敗時に調べる項目
削除に失敗した場合は、GetLastErrorが返す値を失敗直後に取得します。別のAPIを呼んだ後ではエラー情報が上書きされる可能性があるため、ログへ保存してから詳細な調査を行います。
ERROR_FILE_NOT_FOUNDやERROR_PATH_NOT_FOUNDERROR_ACCESS_DENIEDERROR_SHARING_VIOLATIONERROR_DIR_NOT_EMPTYERROR_CURRENT_DIRECTORY
ERROR_SHARING_VIOLATIONは、対象ファイルを別の処理が開いたままにしている場合などに発生します。ログファイル、データベース、画像表示用のファイルでは、ハンドルを閉じ忘れていないか確認します。ERROR_DIR_NOT_EMPTYなら、サブフォルダや隠しファイルを含めて残存項目を列挙します。
再試行する場合も、無制限に繰り返すのは避けます。短い待機を挟んで数回だけ試し、それでも失敗すればエラーコードと対象パスを記録して処理を終了させる方が、ハングや意図しない負荷を防げます。
安全な削除処理を設計する
ユーザーが入力したパスをそのまま削除APIへ渡す設計は危険です。..を含むパスや、アプリケーションの保存先外にあるパスが指定される可能性があるため、基準ディレクトリを決め、正規化後のパスが許可範囲内にあるか検証します。
一時ファイルの整理では、ファイル名の拡張子、作成日時、最終更新日時などの条件を組み合わせます。日時をログへ記録する処理を実装する際は、システム時刻の取得に関するAPIの使い分けも役立ちます。
画面から削除を実行する場合は、対象名を表示して確認を求め、処理中は同じ操作を重複して受け付けないようにします。管理者権限で動作させれば解決するとは限らず、アクセス権を広げる前に、所有者、ACL、ファイルロックの有無を調べることが大切です。
フォントやダイアログなど別のWin32機能と組み合わせたツールでは、リソースの解放漏れも削除失敗の原因になります。画面表示をカスタマイズする場合は、カスタムフォントの指定のようなリソース管理にも目を向けます。
再帰削除と実装上の注意点
フォルダを再帰的に削除する関数では、列挙中の.と..を必ず除外します。また、シンボリックリンクや再解析ポイントを通常のディレクトリと同じようにたどると、想定外の場所まで処理するおそれがあります。属性を確認し、必要に応じてリンク自体だけを対象にします。
ファイルを列挙して削除する流れは、対象一覧を先に作成してから削除する方法と、列挙しながら削除する方法に分けられます。前者は処理の見通しがよく、後者はメモリ使用量を抑えられますが、処理中に内容が変化するケースを考慮しなければなりません。
覚えておくべき要点は、ファイルにはDeleteFile、空のフォルダにはRemoveDirectoryを使い、どちらも戻り値とGetLastErrorを確認することです。パスの検証、Unicode対応、使用中のハンドル、再解析ポイントまで意識すれば、安全で原因を追跡しやすい削除処理になります。