>> WINAPI入門トップに戻る

ファイルの列挙

今回はファイルの列挙について説明します。

フォルダの中にどんなファイルが入っているのかを検索したいときがあると思います。
属性やファイルサイズ等の取得はファイル名がわかっていれば、
前回説明した関数を使って取得することもできますし、
ファイル名さえわかれば、コピーや移動に関連するプログラムも作ることができます。

まず、フォルダの中のファイルを検索するために、
検索ハンドルというものを取得する必要があります。
このハンドルを元にフォルダの中を検索するためです。
これを取得するために、FindFirstFile関数を使います。

HANDLE FindFirstFile(
LPCTSTR lpFileName, // ファイル名
LPWIN32_FIND_DATA lpFindFileData // データバッファ
);

第一引数には検索したいファイル名かディレクトリ名を指定します。
ここにはワイルドカードも含めることが出来ます。
例えば「*.txt」という文字列を指定した場合、
カレントディレクトリ直下の拡張子がtxtになっているファイルが全て対象になります。
全て対象にしたければ「*」とだけしていすれば良いです。
別にファイル単体でも構いません。
「test.txt」と指定すれば、このファイルだけの検索ハンドルが取得できます。
第二引数はWIN32_FIND_DATA型の構造体のポインタを指定します。

成功すると検索ハンドルが返り、
第二引数のポインタにそのファイル情報が格納されます。
失敗すると、INVALID_HANDLE_VALUEの定数が返ります。

WIN32_FIND_DATA型の構造体は以下のような構造になっています。

typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; // ファイル属性
FILETIME ftCreateTime; // 作成日時
FILETIME ftLastAccessTime; // 最終アクセス日時
FILETIME ftLastWriteTime; // 最終更新日時
DWORD nFileSizeHigh; // ファイルサイズ(上位32ビット)
DWORD nFileSizeLow; // ファイルサイズ(下位32ビット)
DWORD dwReserved0; // 予約済み
DWORD dwReserved1; // 予約済み
TCHAR cFileName[MAX_PATH]; // ファイル名
TCHAR cAlternateFileName[14]; // 8.3形式のファイル名
} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;


メンバの内容は上記の通りです。
ファイル属性のdwFileAttributesを使ってファイル属性を調べることも出来ます。
このメンバのビットは、下記の中の定数で示された属性のビット部分が立っています。
以下の定数は自分の属性を表すビット部分が立ってるだけのただの定数です。

FILE_ATTRIBUTE_ARCHIVE	アーカイブファイルまたはアーカイブディレクトリです。アプリケーションはこの属性を、ファイルのバックアップや削除のためのマークとして使います。
FILE_ATTRIBUTE_COMPRESSED	指定されたファイルまたはディレクトリは圧縮されています。ファイルの場合、ファイル内の全データが圧縮されていることを意味します。ディレクトリの場合、そのディレクトリ内に新しく作成されるファイルまたはサブディレクトリが、既定で圧縮状態になることを意味します。
FILE_ATTRIBUTE_DEVICE	予約済み。使わないでください。
FILE_ATTRIBUTE_DIRECTORY	指定されたハンドルは、ディレクトリに関連しています。
FILE_ATTRIBUTE_ENCRYPTED	指定されたファイルまたはディレクトリは暗号化されています。ファイルの場合、ファイル内の全データストリームが暗号化されていることを意味します。ディレクトリの場合、そのディレクトリ内に新しく作成されるファイルまたはサブディレクトリが、既定で暗号化状態になることを意味します。
FILE_ATTRIBUTE_HIDDEN	隠しファイルまたは隠しディレクトリです。通常のディレクトリリスティングでは表示されません。
FILE_ATTRIBUTE_NORMAL	指定されたファイルまたはディレクトリには、特に属性はありません。単独で返った場合にのみ、この属性は有効です。
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED	Windows 2000:このファイルは、「インデックスサービス」の対象になっていません。
FILE_ATTRIBUTE_OFFLINE	Windows 2000:このファイルのデータは、すぐには利用できません。この属性は、ファイルのデータがオフラインの記憶装置へ物理的に移動されたことを示します。この属性は、Windows 2000 の階層記憶管理ソフトウェアである「リモート記憶域」が利用するものです。アプリケーションは、任意にこの属性を変更するべきではありません。
FILE_ATTRIBUTE_READONLY	このファイルまたはディレクトリは読み取り専用です。アプリケーションはこのファイルの読み取りを行えますが、書き込みや削除はできません。ディレクトリの場合、アプリケーションは削除を行えません。
FILE_ATTRIBUTE_REPARSE_POINT	このファイルには、再解析ポイントが関連付けられています。
FILE_ATTRIBUTE_SPARSE_FILE	このファイルは、スパースファイル(疎なファイル、未使用の領域が多い、または同じ値が長く続くファイル)です。
FILE_ATTRIBUTE_SYSTEM	このファイルまたはディレクトリは、オペレーティングシステムの一部、またはオペレーティングシステム専用です。
FILE_ATTRIBUTE_TEMPORARY	このファイルは、一時ファイルとして使われています。ファイルシステムは、データをハードディスクのような大容量記憶装置へ書き込む代わりに、高速なアクセスが行えるよう、すべてのデータをメモリ内に維持することを試みます。アプリケーションは、必要がなくなった段階で一時ファイルをすぐに削除するべきです。


つまり、これらの定数とのビット演算による論理積を求めて、
0以外の値になれば、その属性であるということになります。
もし該当のビットが立っていなかった場合、論理積なのでゼロになりますからね。
基本はディレクトリかどうかを調べるために、「FILE_ATTRIBUT_DIRECTORY」をよく使います。

検索対象のファイルが一つだけであれば、この後は特に何もする必要がありませんが、
ワイルドカードなどを指定して複数のファイルが対象になっている場合は、先程の検索ハンドルを使って
更にファイルを検索することができます。
そのための関数として、FindNextFile関数を使います。

BOOL FindNextFile(
HANDLE hFindFile, // 検索ハンドル
LPWIN32_FIND_DATA lpFindFileData // データバッファ
);

第一引数にはFindFirstFile関数で取得した検索ハンドルを指定します。
第二引数にはWIN32_FIND_DATA型の構造体のポインタを指定します。
成功すると、第二引数のポインタにファイルの情報が格納され、戻り値は0以外の値を返します。
失敗する、もしくはこれ以上検索対象のファイルが無い場合は0を返します。
つまり0が返るまでループし続ければ、対象のファイル全ての情報が取得できるわけです。

ちなみに0を返したときはエラーなのか本当に検索対象のファイルが無いのか、どちらなのか分かりませんよね?
それを調べるために、GetLastError関数を使います。
この関数はこの関数実行前に発生した最後のエラー情報を戻りととして返してくれます。
引数はありません。
この関数の戻り値の値が、「ERROR_NO_MORE_FILES」だった場合は、
検索対象のファイルが無いということになります。

最後に、検索が全て終わったら、FindClose関数で、
検索ハンドルを閉じる必要があります。

BOOL FindClose(
HANDLE hFindFile // ファイル検索ハンドル
);

引数は検索ハンドルを指定するだけです。

以上の関数を使って、カレントディレクトリのファイル及びフォルダを列挙するサンプルを作成しました。
以下のコードをご覧下さい。

#include <windows.h>
#include <stdio.h>
#include <string.h>

#define MSG(m) {\
	MessageBoxA(NULL,m,NULL,MB_OK);}

//ウィンドウハンドル
HWND hwnd;
//インスタンスハンドル
HINSTANCE hinst;

//ウィンドウ横幅
#define WIDTH 500
#define HEIGHT 300



LRESULT CALLBACK WinProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
	char buf[10000];
	
	static HANDLE h;

	WIN32_FIND_DATA find;

	switch(msg){
		case WM_DESTROY:

			PostQuitMessage(0);
			return 0;

		case WM_LBUTTONDOWN:

			
			//バッファ初期化
			memset(buf,0,sizeof(buf));

			h = FindFirstFile("*",&find);

			if(h!=INVALID_HANDLE_VALUE){

				//まずfirdfirstfileによって得られたファイル情報を取得する。
				//ディレクトリかどうかチェック
				if(find.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY){

					strcat(buf,"【ディレクトリ】");

				//ファイル
				}else{

					strcat(buf,"【ファイル】");
				}


				strcat(buf,find.cFileName);
				strcat(buf,"\n");

				//検索対象のファイルがあるまでループ。
				while(1){

					if(FindNextFile(h,&find)){


						//ディレクトリかどうかチェック
						if(find.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY){

							strcat(buf,"【ディレクトリ】");

						//ファイル
						}else{

							strcat(buf,"【ファイル】");
						}


						strcat(buf,find.cFileName);
						strcat(buf,"\n");

					}else{
						if(GetLastError()==ERROR_NO_MORE_FILES){
							//ファイルが無い場合は終了
							MSG("列挙完了。表示します");
							MSG(buf);
							FindClose(h);
							return 0;
						}else{
							MSG("何らかのエラーが発生しました");
							FindClose(h);
							return -1;
							
						}
					}
				}
			}else{
				MSG("FindFirstFileで不明なエラーが発生しました");
				return -1;
			}

			return 0;

		
	}
	return DefWindowProc(hwnd,msg,wp,lp);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
	MSG msg;
	WNDCLASS wc;

	wc.style=CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc=WinProc;
	wc.cbClsExtra=wc.cbWndExtra=0;
	wc.hInstance=hInstance;
	wc.hCursor=wc.hIcon=NULL;
	wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszClassName="test";
	wc.lpszMenuName=NULL;
	
	if(!RegisterClass(&wc)){
		MSG("クラスの登録失敗");
		return -1;
	}

	
	//インスタンスハンドル
	hinst=hInstance;

	hwnd=CreateWindowA("test","テストウィンドウ",WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		0,0,400,400,NULL,NULL,hInstance,NULL);


	if(hwnd==NULL){
		MSG("ウィンドウ作成失敗");
		return -1;
	}


	//エラーチェック用変数
	int check;

	while(check=GetMessage(&msg,NULL,0,0)){
		if(check==-1){
			break;
		}
		DispatchMessage(&msg);
	}

	//クラス解放
	UnregisterClass("test",hinst);

	return 0;

}

コレを実行してクライアント領域を左クリックすると、
「列挙完了。表示します」というメッセージが現われたあとに、
カレントディレクトリのファイル及びフォルダをポップアップで表示します。

毎回strcat関数を使って、改行しながら、ファイルかフォルダかを調べて、
メッセージをつなげています。
ファイルが見つからなくなったら終了し、
列挙してつなげた文字列をポップアップで出力するようにしています。

以上がファイル列挙の説明になります。

次回は、コマンドライン引数の取得について説明します。


>> 【コマンドライン引数の取得】に進む
>> WINAPI入門トップに戻る