スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【コード】フォルダ内のファイルを全て列挙する

アーカイブファイルが作りたくて始めてみました。
とりあえず第一歩を踏み出すべく、
フォルダ内のファイルを全てテキストに書きだしてみました。
予想以上に難しかったです…

一フォルダ内のファイルを列挙するだけならすぐ終わったのですが
その中にフォルダがあった場合の処理で躓いてました。
というのも、フォルダ内も一緒に検索してくれる関数があると思っていたためです。

しかしそんな都合のよい関数までは用意されていませんでした;
なので、自分で文字列をくっつけたりしながらフォルダを調べていく作業になりました。


とりあえず完成はしましたが、サンプルのためクラス化はしてません。
このサンプルは、マルチバイト文字セットを使って作成しております。
(一応 Unicode ビルドでも通りそうだけど、多分期待した出力結果にならないはず)


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

std::ofstream gOfs(_T("SampleFileList.txt")); // ファイルストリーム

INT APIENTRY _tWinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, INT nWinMode)
{
setlocale(LC_ALL, "Japanese");

// ファイルが開けなかった
if(!gOfs.is_open())
return 0;

// 既にウインドウは作成されているものとしています

// カレントディレクトリを取得する
TCHAR wCurDir[MAX_PATH + 1];
GetCurrentDirectory(MAX_PATH+1, wCurDir);
SetCurrentDirectory(wCurDir);

EnumFolder(wCurDir); // フォルダ検索

MessageBox(gWindow->GetWndHandle(), _T("ファイルの列挙が終了"), _T("[終了]"), MB_OK);

return 0;
}


このプログラムは既にウインドウは作成されているものとしています。

プログラムが始まるとカレントディレクトリを取得しています。

TCHAR wCurDir[MAX_PATH + 1];
GetCurrentDirectory(MAX_PATH+1, wCurDir);
SetCurrentDirectory(wCurDir);

GetCurrentDirectory でカレントディレクトリが取得できます。
そのディレクトリを SetCurrentDirecotry でデフォルトのカレントディレクトリにしています。

次の EnumFolder(wCurDir); に、今のカレントディレクトリを渡します。
この EnumFolder 関数でファイル名を列挙していく事になります。


//***************************************************
// 指定したフォルダ内のファイルを全て列挙
// 第一引数 : 検索対象ディレクトリ
// 戻り値 : なし
//***************************************************
void EnumFolder(const std::tstring &Str)
{
HANDLE whSearch;
WIN32_FIND_DATA wFindData;
std::tstring wFileName = Str + _T("\\*.*"); // 検索文字にワイルドカードを指定

// 一番最初のファイル検索(なければ終了)
if( (whSearch = FindFirstFile(wFileName.c_str(), &wFindData)) == INVALID_HANDLE_VALUE )
return;

// ファイルの列挙が終了するか、エラーが出るまでループする
while(1)
{
// . .. はファイル名として読み込まない
if( (_tcscmp(wFindData.cFileName, _T(".")) != 0) && (_tcscmp(wFindData.cFileName, _T("..")) != 0) )
{
// ディレクトリかどうかを判断する
if(wFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
std::tstring wSubFolder = Str + _T("\\") + wFindData.cFileName; // フォルダを検索するための文字列連結
gOfs<< _T("[Directory]")<< wFindData.cFileName<< _T("\r\n");
EnumFolder(wSubFolder);
}
else
gOfs<< wFindData.cFileName<< _T("\r\n");
}

// 次のファイルを検索する
if(!FindNextFile(whSearch, &wFindData))
{
// エラーでないなら列挙終了
if(GetLastError() != ERROR_NO_MORE_FILES)
{
MessageBox(gWindow->GetWndHandle(), _T("エラー!!"), _T("[エラー]"), MB_OK );
break;
}

break;
}
}

FindClose(whSearch); // 使い終わったハンドルを閉じる
}

列挙するのに使う関数は三つで、

・FindFirstFile
・FindNextFile
・FindClose

の三つです。
最初のファイルを検索→続きを検索→検索が終わればクローズしてお終い
この流れだけですが結構悩んでました;


HANDLE whSearch;
このハンドル(whSearch)を使ってファイルを検索していく事になります。

WIN32_FIND_DATA wFindData;
WIN32_FIND_DATA 構造体に読み込んだファイル名が入れられたりします。

std::tstring wFileName = Str + _T("\\*.*");
引数として送られてきた Str には「カレントディレクトリ」が入っています。
なので、「カレントディレクトリ\*.*」が wFileName になります。

*.* の意味ですが、「*」はワイルドカードと言うそうです。
*.* とすることで、ファイル名はなんでもOK . 拡張子なんでもOK
と言う意味になるようなので、
例えば、.txt の拡張子を取得したいなら「*.txt」とすればよいという事です。



この関数は再帰呼び出しを行っているのですが
まずはカレントディレクトリにあるファイルを全て読み込もうとしています。

また、std::tstring に関しては TCHAR 型の string クラスです。
コレについては 先日書いた記事 があるのですが

namespace std
{
typedef basic_string<TCHAR, char_traits<TCHAR>, allocator<TCHAR>>
tstring;
};

basic_string の TCHAR 版を作ったので std::tstring として扱えてます。
マルチバイト文字セットなら std::string で代用しても構わないと思います



if( (whSearch = FindFirstFile(wFileName.c_str(), &wFindData)) == INVALID_HANDLE_VALUE )
 return;

次のこの部分で一番最初のファイル検索しています。
FindFirstFile の第一引数に「ファイル名」 第二引数に「WIN32_FIND_DATA 構造体のアドレス」を渡します。

上手くファイルが読み込めると wFindData のメンバである cFileName に読み込んだファイル名が入ります。
もしエラー(INVALID_HANDLE_VALUE)が出たら終了します。


どのくらいファイルの数があるか解らないので while を使って無限ループさせてます。
終了条件は 検索するファイルがなくなったエラーが出た かになると思います。


if( (_tcscmp(wFindData.cFileName, _T(".")) != 0) && (_tcscmp(wFindData.cFileName, _T("..")) != 0) )

wFindData.cFileName には始め「.」が読み込まれ、次は「..」が読み込まれます。
フォルダを指定する度に読み込まれますが、この情報は必要ないので
_tcscmp を使って「.」「..」の場合は処理しないような条件にしてあります。


何もしなければ、フォルダもファイル名として出力する事になります。
なので、どこかでフォルダかどうかを調べる必要がある訳ですが

if(wFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)

それがこの部分で、
wFindData.dwFileAttributes と FILE_ATTRIBUTE_DIRECTORY を
AND演算すれば解るみたいです。

この結果が true であればフォルダだと言う事が解るので、
フォルダの場合は再帰処理をします。
今のパスに、フォルダ名をくっつければ次に検索すべきフォルダが解るので
それをまた検索して行くという感じです。

それが、

std::tstring wSubFolder = Str + _T("\\") + wFindData.cFileName;
gOfs<< _T("[Directory]")<< wFindData.cFileName<< _T("\r\n");
EnumFolder(wSubFolder);

の部分です。
このプログラムでは、フォルダだと言う事が解るように
ファイル出力の所で [Directory] と言う文字を付加して出力してます。
(改行"\r\n" はバイナリ形式の改行です)

Str にはカレントディレクトリ、wFindData.cFileName には今フォルダ名が入っていますので
「カレントディレクトリ\フォルダ名」が得られます。

これをまた EnumFolder 関数に送ると
std::tstring wFileName = Str + _T("\\*.*");
の部分で、ワイルドカードが文字列として連結され、そのフォルダに対してまた検索をかけていきます。
その中にフォルダがあれば再帰処理なので段々検索していきます。


フォルダでなければ普通のファイルと言う事になるので、
単純にファイルに書き込むだけです(改行はバイナリ形式です)
gOfs<< wFindData.cFileName<< _T("\r\n");


if(!FindNextFile(whSearch, &wFindData))

一つ読み込めば次のファイルを読み込んで行きます。
FindNextFile の第一引数に「ハンドル」
第二引数に「WIN32_FIND_DATA 構造体のアドレス」を指定します。


ファイルを読み込んだ時に、エラーなのかどうかを判断しているのが次の部分です。

if(GetLastError() != ERROR_NO_MORE_FILES)
{
  MessageBox(gWindow->GetWndHandle(), _T("Error!!"), _T("[Error]"), MB_OK );
  break;
}

ERROR_NO_MORE_FILES でなければ読み込み失敗です。
そうでなければ、そのフォルダでのファイルは全て読み込んだ という意味になります。


これでフォルダ内のファイルを全て列挙してテキストに出力する事が出来ました。
検索される順番はファイル名で決まっているみたいなので
フォルダ検索中にフォルダが見つかると、再帰処理のためその中に入って行きます。
なので、出力されるファイルはフォルダを優先に出力されていきます。

フォルダ内の検索が終われば、違うファイルを読み込みますので
このフォルダにはコレ! このフォルダにはコレ!…と
まとめるためには処理自体を変える必要はありますが

今の所は練習と言う事でこれでいいかなと思いました。
とりあえずアーカイブへの第一歩を踏み出せたんじゃないかなと思います。

圧縮とかについては今は全く考慮していないので
とにかくフォルダを一つにまとめて、それをゲームで使うと言う事が目標です。
先は長そうですが頑張りたいと思います。

この記事へのコメント

トラックバック

URL :

検索フォーム
プロフィール

DVDM

Author:DVDM
自作ゲームの開発過程ブログ。
赤髪愛なら誰にも負けない。

 
Pixiv バナー


ブロとも申請フォーム
最新記事
カテゴリ
最新コメント
最新トラックバック
RSSリンクの表示
リンク
ブロとも一覧
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。