ファイルやフォルダの検索と列挙

ファイルやフォルダの検索

特定のディレクトリ内にあるファイルやディレクトリを検索するにはFindFirstFile関数、FindNextFile関数、およびFindClose関数を使用します。

FindFirstFile関数

HANDLE FindFirstFileW(
 LPCWSTR lpFileName,
 LPWIN32_FIND_DATAW lpFindFileData
);
ファイル名lpFileNameに最初に一致するファイルまたはディレクトリを検索し、WIN32_FIND_DATA構造体変数lpFindFileDataに格納する。
戻り値はファイル検索に使用する検索ハンドル。
失敗した場合はINVALID_HANDLE_VALUEを返す。

第一引数lpFileNameはファイル名、ディレクトリ名、またはパスを指定します。
これにはワイルドカードを使用することができます。

ワイルドカード

ワイルドカードは文字列を柔軟に指定するための特殊文字です。

「?」は任意の一文字を意味します。

  • 200?年→「2001年」「200a年」などにヒット

「*」は任意の文字列を意味します。
0文字にもヒットします。

  • 2*年→「2年」「234年」「2abc年」などにヒット
  • *.txt→「abc.txt」「1234.txt」などにヒット
  • *.*→「abc.jpg」「1234.xml」などにヒット

例えば「C:\abc」内にあるファイルとフォルダをすべて検索する場合は「C:\abc\*」を指定します。
(エスケープシーケンスを含めて記述すると"C:\\abc\\*"です)

第二引数lpFindFileDataWIN32_FIND_DATA構造体で、見つかったファイルの情報を格納します。

typedef struct _WIN32_FIND_DATAW {
 DWORD dwFileAttributes;
 FILETIME ftCreationTime;
 FILETIME ftLastAccessTime;
 FILETIME ftLastWriteTime;
 DWORD nFileSizeHigh;
 DWORD nFileSizeLow;
 DWORD dwReserved0;
 DWORD dwReserved1;
 WCHAR cFileName[MAX_PATH];
 WCHAR cAlternateFileName[14];
 DWORD dwFileType; //廃止
 DWORD dwCreatorType; //廃止
 WORD wFinderFlags; //廃止
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
FindFirstFile、FindFirstFileEx、FindNextFile関数により検索されたファイル情報を格納する構造体。

メンバが多いですがほとんどは以前に説明したものばかりです。
(「ファイル」と説明していますがディテクトリも扱います)

メンバ 説明
dwFileAttributes ファイルの属性定数
(GetFileAttributes関数を参照)
ftCreationTime
ftLastAccessTime
ftLastWriteTime
ファイルの作成日時
ファイルの最終アクセス日時
ファイルの最終更新日時
(日時情報を参照)
nFileSizeHigh
nFileSizeLow
ファイルサイズの上位ダブルワード
ファイルサイズの下位ダブルワード
(ファイルサイズを参照)
cFileName ファイル名
cAlternateFileName ファイルの別名
(8.3形式)

dwReserved0dwReserved1dwFileTypedwCreatorTypewFinderFlagsメンバは使用することはありません。

FindFirstFile関数は、検索文字列にワイルドカードを使用し、見つかったファイル名をWIN32_FIND_DATA構造体のcFileNameメンバから取得する、というのが一般的な使用方法です。

戻り値はHANDLE型で、これは続けて検索する際に使用します。
FindFirstFile関数は「最初に見つかったファイル(ディレクトリ)」を取得します。
条件に一致するファイルを複数取得する場合はFindNextFile関数を使用して次のファイルを検索します。
検索に失敗した場合はINVALID_HANDLE_VALUEを返します。

FindNextFile関数

条件に一致する次のファイルを検索するにはFindNextFile関数を使用します。

BOOL FindNextFileW(
 HANDLE hFindFile,
 LPWIN32_FIND_DATAW lpFindFileData
);
検索ハンドルhFindFileを使用してファイル、ディレクトリを検索しWIN32_FIND_DATA構造体変数lpFindFileDataに格納する。
成功した場合は0以外を、失敗した場合は0を返す。

第一引数hFindFileFindFirstFile関数の戻り値である検索ハンドルを指定します。
これを元にして、前回と同じ条件で一致する次のファイルを検索します。

第二引数lpFindFileDataはFindFirstFile関数と同じです。

FindClose関数

必要な検索を終えたら、最後にFindClose関数で検索ハンドルを閉じます。

BOOL FindClose(
 HANDLE hFindFile
);
検索ハンドルhFindFileを閉じる。
成功した場合は0以外を、失敗した場合は0を返す。

ファイルとフォルダの列挙

以下のコードはカレントディレクトリにあるファイルやフォルダをすべて列挙します。


#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define IDC_EDIT1 100

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hEdit;
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	WCHAR path[MAX_PATH];

	switch (message)
	{
	case WM_CREATE: //ウィンドウの作成
		hEdit = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"EDIT", L"",
			WS_CHILD | WS_VISIBLE |
			ES_READONLY | ES_MULTILINE | WS_HSCROLL | WS_VSCROLL,
			10, 10, 480, 350,
			hWnd, (HMENU)IDC_EDIT1, hInst, NULL);

		//カレントディレクトリの取得
		GetCurrentDirectory(MAX_PATH, path);
		//パス末尾に「\*」を追加して
		//全てのファイルとディレクトリを検索対象にする
		StringCchCatN(path, MAX_PATH, L"\\*", 2);

		//検索開始
		hFind = FindFirstFile(path, &fd);
		if (hFind == INVALID_HANDLE_VALUE)
			break;

		do {
			//見つかったファイル(ディレクトリ)を
			//エディットコントロールの末尾に追記して改行
			SendMessage(hEdit, EM_REPLACESEL, FALSE, fd.cFileName);
			SendMessage(hEdit, EM_REPLACESEL, FALSE, L"\r\n");
		//FindNextFile関数がFALSEを返すまで繰り返し
		} while (FindNextFile(hFind, &fd));
		FindClose(hFind);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

ファイルの検索のサンプル

Visual Studioからデバッグモードで起動した時の、カレントディレクトリにあるファイルとフォルダの一覧が表示されています。

「.」は現在のディレクトリ、「..」はひとつ上のディレクトリを表す特殊な文字です。
Windowsエクスプローラー上には表示されません。

サブディレクトリ内も含めてファイルとフォルダを列挙

サブディレクトリ(ディレクトリ内にあるディレクトリ)の中まで含めて検索するには関数の再帰呼び出しを利用する方法があります。


#pragma comment(lib, "Shlwapi.lib")

#include <windows.h>
#include <strsafe.h>
#include <Shlwapi.h>

//関数プロトタイプ宣言
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL MyPathCombine(WCHAR*, size_t, const WCHAR*, const WCHAR*);
BOOL _FindAllFiles(const WCHAR*, HWND, int, int*, int*);
BOOL FindAllFiles(const WCHAR*, HWND, int*, int*);

//ウィンドウの生成等は省略

#define IDC_EDIT1 100
#define IDC_BUTTON1 101
#define IDC_EDIT2 102
#define IDC_STATIC1 103

//パス文字列path1とパス文字列path2を結合して文字列バッファdstに格納する
//区切り文字は適切に処理される
//FALSEを返すとき、バッファdstSizeの中身は不定
BOOL MyPathCombine(WCHAR* dst, size_t dstSize, const WCHAR* path1, const WCHAR* path2) 
{
	if (dstSize == 0)
		return FALSE;

	//それぞれのパスのサイズを取得
	size_t path1len, path2len, cchMin;
	cchMin = dstSize < STRSAFE_MAX_CCH ? dstSize : STRSAFE_MAX_CCH;
	if (FAILED(StringCchLength(path1, cchMin, &path1len))) {
		if (path1 == NULL) {
			path1 = L"";
			path1len = 0;
		}
		else {
			return FALSE;
		}		
	}		
	if (FAILED(StringCchLength(path2, cchMin, &path2len))) {
		if (path2 == NULL) {
			path2 = L"";
			path2len = 0;
		}
		else {
			return FALSE;
		}
	}

	//一方のサイズが0ならもう一方のみをバッファにコピーして終了
	if (path1len == 0) {
		if (FAILED(StringCchCopyN(dst, dstSize, path2, path2len)))
			return FALSE;
		return TRUE;
	}
	if (path2len == 0) {
		if (FAILED(StringCchCopyN(dst, dstSize, path1, path1len)))
			return FALSE;
		return TRUE;
	}

	//path2が絶対パスならpath2のみをバッファにコピーして終了
	if (!PathIsRelative(path2)) {
		if (FAILED(StringCchCopyN(dst, dstSize, path2, path2len)))
			return FALSE;
		return TRUE;
	}

	//path1をバッファにコピー
	if (FAILED(StringCchCopyN(dst, dstSize, path1, path1len)))
		return FALSE;

	//path1の末尾が区切り文字ならpath2の先頭の区切り文字を除去
	if (path1[path1len - 1] == L'\\') {
		if (path2[0] == L'\\') {
			path2++;
			path2len--;
		}
	}
	//path1の末尾が区切り文字でなく、
	//path2の先頭が区切り文字でなければ区切り文字を追加
	else {
		if (path2[0] != L'\\')
			if (FAILED(StringCchCat(dst, dstSize, L"\\")))
				return FALSE;
	}
	if (FAILED(StringCchCatN(dst, dstSize, path2, path2len)))
		return FALSE;
	return TRUE;
}

//パスpath内にあるファイルとフォルダを列挙してエディットコントロールhEditに出力する
//recursiveは再帰呼び出しの回数
//見つかったファイル数はfileCount、フォルダ数はdirCountに格納する
//成功した場合はTRUE、失敗した場合はFALSEを返す。
BOOL _FindAllFiles(const WCHAR* path, HWND hEdit, int recursive, int* fileCount, int* dirCount)
{
	*fileCount = 0;
	*dirCount = 0;

	//エディットコントロールの初期化
	if (recursive == 0)
		SetWindowText(hEdit, L"");

	WCHAR strSearch[MAX_PATH];
	
	//パス末尾を「\*」にして
	//全てのファイルとディレクトリを検索対象にする
	if (!MyPathCombine(strSearch, MAX_PATH, path, L"*")) {
		return FALSE;
	}

	HANDLE h;
	WIN32_FIND_DATA fd;

	//最初の検索を開始
	h = FindFirstFile(strSearch, &fd);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	//再帰呼び出し用の
	//ファイル数とディレクトリ数のカウンタ
	int _fileCount = 0;
	int _dirCount = 0;

	//インデント(字下げ)
	int indent;

	//引数のパスと検索結果を結合するためのバッファ
	WCHAR _path[MAX_PATH];

	//エディットコントロールの文字列の長さを格納する
	//(末尾に追記するために使用)
	int editLength;

	do {
		//「.」「..」のみのファイル名は無視
		if (lstrcmpi(fd.cFileName, L".") == 0 ||
			lstrcmpi(fd.cFileName, L"..") == 0)
			continue;

		indent = recursive;

		//エディットコントロールのキャレットを末尾に移動
		editLength = GetWindowTextLength(hEdit);
		SendMessage(hEdit, EM_SETSEL, editLength, editLength);
		//サブディレクトリの場合は階層数分の字下げ
		while (indent--)
		{
			SendMessage(hEdit, EM_REPLACESEL, FALSE, L" ");
		}
		//見つかったファイル(ディレクトリ)を追記
		SendMessage(hEdit, EM_REPLACESEL, FALSE, fd.cFileName);
		SendMessage(hEdit, EM_REPLACESEL, FALSE, L"\r\n");

		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			//ディレクトリが見つかった
			(*dirCount)++;
			//パスにディレクトリ名を結合
			if (!MyPathCombine(_path, MAX_PATH, path, fd.cFileName)) {
				continue;
			}
			//再帰呼び出しでサブディレクトリを検索
			if (_FindAllFiles(_path, hEdit, recursive + 1, &_fileCount, &_dirCount)) {
				*fileCount += _fileCount;
				*dirCount += _dirCount;
			}
		}
		else {
			//ファイルが見つかった
			(*fileCount)++;
		}
		//FindNextFileがFALSEを返すまでループ
	} while (FindNextFile(h, &fd));

	//検索ハンドルを閉じる
	FindClose(h);
	return TRUE;
}

//パスpath内にあるファイルとフォルダを列挙してエディットコントロールhEditに出力する
//見つかったファイル数はfileCount、フォルダ数はdirCountに格納する
//成功した場合はTRUE、失敗した場合はFALSEを返す。
BOOL FindAllFiles(const WCHAR* path, HWND hEdit, int* fileCount, int* dirCount)
{
	//再帰呼び出しの回数に「0」を指定して_FindAllFiles関数を実行
	return _FindAllFiles(path, hEdit, 0, fileCount, dirCount);
}

#define BUFFERSIZE 64

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hEdit1, hEdit2, hButton, hStatic;
	WCHAR path[MAX_PATH];
	RECT rt;
	int fileCount, dirCount;
	WCHAR buf[BUFFERSIZE];

	switch (message)
	{
	case WM_CREATE: //ウィンドウの作成
		GetCurrentDirectory(MAX_PATH, path);

		hEdit1 = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"EDIT", path,
			WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
			10, 10, 440, 24,
			hWnd, (HMENU)IDC_EDIT1, hInst, NULL);
		hButton = CreateWindow(
			L"BUTTON", L"検索",
			WS_CHILD | WS_VISIBLE,
			450, 10, 40, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);

		hStatic = CreateWindow(
			L"STATIC", L"",
			WS_CHILD | WS_VISIBLE,
			10, 35, 480, 24,
			hWnd, (HMENU)IDC_STATIC1, hInst, NULL);

		hEdit2 = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"EDIT", L"",
			WS_CHILD | WS_VISIBLE |
			ES_READONLY | ES_MULTILINE | WS_HSCROLL | WS_VSCROLL,
			10, 60, 480, 300,
			hWnd, (HMENU)IDC_EDIT2, hInst, NULL);
		break;

	case WM_SIZE: //ウィンドウのリサイズ
		GetClientRect(hWnd, &rt);
		SetWindowPos(hEdit1, 0, 0, 0, rt.right - 20 - 40, 24, SWP_NOMOVE | SWP_NOZORDER);
		SetWindowPos(hButton, 0, rt.right - 10 - 40, 10, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
		SetWindowPos(hStatic, 0, 0, 0, rt.right - 20, 24, SWP_NOMOVE | SWP_NOZORDER);
		SetWindowPos(hEdit2, 0, 0, 0, rt.right - 20, rt.bottom - 70, SWP_NOMOVE | SWP_NOZORDER);
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1: //検索ボタン
			GetWindowText(hEdit1, path, MAX_PATH);
			//出力用エディットコントロールの描画停止
			SendMessage(hEdit2, WM_SETREDRAW, FALSE, 0);
			if (FindAllFiles(path, hEdit2, &fileCount, &dirCount)) {
				StringCchPrintf(buf, BUFFERSIZE, L"ファイル: %d個, フォルダ: %d個", fileCount, dirCount);				
			}
			else {
				StringCchPrintf(buf, BUFFERSIZE, L"検索失敗");
			}
			//出力用エディットコントロールの描画再開
			SendMessage(hEdit2, WM_SETREDRAW, TRUE, 0);
			SetWindowText(hStatic, buf);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

サブディレクトリ内にあるファイルやディレクトリは字下げ(インデント)して表示しています。
サブディレクトリも含めてファイルを列挙

ファイルとディレクトリの判別はPathIsDirectory関数を使用する方法もありますが、WIN32_FIND_DATA構造体のdwFileAttributesメンバにファイルの属性フラグが格納されています。
このフラグにFILE_ATTRIBUTE_DIRECTORYが含まれていればそれはディレクトリと判別できます。

パス操作関数について

上記コードでは、二つのパス文字列を結合するためにMyPathCombineという自作関数を定義しています。
この関数は、区切り文字の有無に依らずにパスを適切に処理します。
(二つのパス間で区切り文字が重複したり区切り文字がなかったり、といった心配が無い)

これと同等のWin32APIはPathAppend関数がありますが、これはバッファオーバーランの危険性がある非推奨関数です。
Windows8以降の環境ならば安全なPathCchAppend関数が使用できるので、可能ならそちらを使用すると良いでしょう。
(おそらく自作のMyPathCombine関数よりも高速)

また、MyPathCombine関数の中ではPathIsRelativeという関数を使用しています。
これは引数に指定したパスが相対パスならTRUEを返します。
詳しくはリンク先ページを参照してください。

PathIsRelative関数の使用には、Shlwapi.libのリンクとShlwapi.hのインクルードが必要です。


#pragma comment(lib, "Shlwapi.lib")

#include <windows.h>
#include <Shlwapi.h>

ファイルとディレクトリ操作のサンプル

FindFirstFile関数と再帰呼び出しを利用して、ディレクトリ内のファイルやサブディレクトリを操作するサンプルコードです。
ただし大抵の操作はSHFileOperation関数を使用したほうが簡単に実現できます。

空ではないディレクトリを削除する

RemoveDirectory関数はディレクトリを削除しますが、中身が空ではないディレクトリは削除できません。
空ではないディレクトリを削除するコードのサンプルです。


#pragma comment(lib, "Pathcch.lib")

#include <windows.h>
#include <pathcch.h>

//関数プロトタイプ宣言
BOOL MyRemoveDirectory(const WCHAR*);

//ディレクトリdirを削除する
//ディレクトリが空でなくても削除可能
BOOL MyRemoveDirectory(const WCHAR *dir)
{
	if (dir == NULL)
		return FALSE;

	WCHAR search[MAX_PATH];
	//パス末尾を「\*」にして
	//全てのファイルとディレクトリを検索対象にする
	if (FAILED(PathCchCombine(search, MAX_PATH, dir, L"*"))) {
		//パス文字列が長すぎる
		return FALSE;
	}

	HANDLE h;
	WIN32_FIND_DATA fd;

	//最初の検索を開始
	h = FindFirstFile(search, &fd);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	WCHAR buf[MAX_PATH];

	do {
		//「.」「..」のみのファイル名は無視
		if (lstrcmpi(fd.cFileName, L".") == 0 ||
			lstrcmpi(fd.cFileName, L"..") == 0)
			continue;

		//引数のパスと検索結果を結合
		if (FAILED(PathCchCombine(buf, MAX_PATH, dir, fd.cFileName))) {
			//パス文字列が長すぎる
			continue;
		}

		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			//ディレクトリが見つかった
			//再帰呼び出しでディレクトリ内を削除
			MyRemoveDirectory(buf);
		}
		else {
			//ファイルの場合は読み取り専用属性を外して削除
			SetFileAttributes(buf, GetFileAttributes(buf) & ~FILE_ATTRIBUTE_READONLY);
			DeleteFile(buf);
		}

	} while (FindNextFile(h, &fd));
	FindClose(h);

	return RemoveDirectory(dir);
}

ファイルとフォルダの列挙と処理自体は非常に似ています。
ファイルが見つかった場合はそのまま削除します。
(読み取り専用属性がある場合は削除できないので、属性を外します)
フォルダが見つかった場合は再帰呼び出しでその中身に対して同じ処理を行い、最後にフォルダ自身を削除します。

なお、今回はパス文字列の結合には自作関数ではなくPathCchCombine関数関数を使用しています。
この関数の成否は戻り値にSUCCEEDEDマクロまたはFAILEDマクロを使用して取得します。
SUCCEEDEDマクロは関数が成功したらTRUEを返し、FAILEDマクロは関数が失敗したらTRUEを返します。

PathCchCombine関数の使用には、Pathcch.libのリンクとPathcch.hのインクルードが必要です。


#pragma comment(lib, "Pathcch.lib")

#include <windows.h>
#include <Pathcch.h>

なお、Windows7以前の環境ではこの関数は使用できません。

ディレクトリをコピーする

ディレクトリを中身ごとコピーするコードのサンプルです。


#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "Pathcch.lib")

#include <windows.h>
#include <strsafe.h>
#include <Shlwapi.h>
#include <pathcch.h>

//関数プロトタイプ宣言
void StringCharReplace(WCHAR*, size_t, WCHAR, WCHAR);
BOOL _MyCopyDirectory(const WCHAR*, const WCHAR*);
BOOL MyCopyDirectory(const WCHAR*, const WCHAR*);

#define BUFFERSIZE (MAX_PATH + 33)

//文字列strのlength文字目までの文字searchを文字replaceに置き換える
//元の文字列自身を置き換える破壊的処理なので
//引数strに文字列リテラルを指定するとエラー
void StringCharReplace(WCHAR* str, size_t length, WCHAR search, WCHAR replace) 
{
	while (*str && length--) {
		if (*str == search) {
			*str = replace;
		}
		str++;
	}
}

//ディレクトリsrcをディレクトリdstにコピーする
BOOL MyCopyDirectory(const WCHAR* src, const WCHAR* dst)
{
	//_MyCopyDirectory関数は再帰呼び出しを行うので
	//この関数内で引数のチェックと整形を行う

	if (src == NULL || dst == NULL)
		return FALSE;

	WCHAR _src[MAX_PATH];
	WCHAR _dst[MAX_PATH];

	StringCchCopy(_src, MAX_PATH, src);
	StringCchCopy(_dst, MAX_PATH, dst);

	//パス区切り文字の/を\に置き換え
	StringCharReplace(_src, MAX_PATH, L'/', L'\\');
	StringCharReplace(_dst, MAX_PATH, L'/', L'\\');

	//パス末尾が\ならば削除しておく
	if (_src[lstrlen(_src) - 1] == L'\\')
		_src[lstrlen(_src) - 1] = L'\0';
	if (_dst[lstrlen(_dst) - 1] == L'\\')
		_dst[lstrlen(_dst) - 1] = L'\0';

	if (lstrcmpi(_src, _dst) == 0) {
		MessageBox(NULL, L"送り側と受け側のパスが同じです。", L"エラー", MB_OK);
		return FALSE;
	}

	return _MyCopyDirectory(_src, _dst);
}

//再帰呼び出しを利用してディレクトリsrcをディレクトリdstにコピーする
//高速化のため引数のチェックを行わないので
//使用する場合はMyCopyDirectory関数から実行する
BOOL _MyCopyDirectory(const WCHAR* src, const WCHAR* dst)
{	
	WCHAR buf[BUFFERSIZE];

	//存在しない場合にフォルダを作成
	if (PathFileExists(dst)) {
		if (!PathIsDirectory(dst)) {
			StringCchPrintf(buf, BUFFERSIZE, L"送り側に同名のファイルが存在するためフォルダーを作成できません。\n%s", src);
			MessageBox(NULL, buf, L"エラー", MB_OK);
			return FALSE;
		}
	}
	else {
		if (!CreateDirectory(dst, NULL)) {
			StringCchPrintf(buf, BUFFERSIZE, L"フォルダーを作成できませんでした。\n%s", dst);
			MessageBox(NULL, buf, L"エラー", MB_OK);
			return FALSE;
		}
	}

	WCHAR search[MAX_PATH];
	//パス末尾を「\*」にして
	//全てのファイルとディレクトリを検索対象にする
	if (FAILED(PathCchCombine(search, MAX_PATH, src, L"*"))) {
		//パス文字列が長すぎる
		return FALSE;
	}

	HANDLE h;
	WIN32_FIND_DATA fd;

	//最初の検索を開始
	h = FindFirstFile(search, &fd);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	WCHAR _src[MAX_PATH];
	WCHAR _dst[MAX_PATH];

	do {
		//「.」「..」のみのファイル名は無視
		if (lstrcmpi(fd.cFileName, L".") == 0 ||
			lstrcmpi(fd.cFileName, L"..") == 0)
			continue;

		//引数のパスと検索結果を結合
		if (FAILED(PathCchCombine(_src, MAX_PATH, src, fd.cFileName)) ||
			FAILED(PathCchCombine(_dst, MAX_PATH, dst, fd.cFileName))) {
			//パス文字列が長すぎる
			continue;
		}

		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			//ディレクトリが見つかった
			//再帰呼び出しでサブディレクトリをコピー
			_MyCopyDirectory(_src, _dst);
		}
		else {
			//ファイルが見つかった
			if (!CopyFile(_src, _dst, FALSE)) {
				StringCchPrintf(buf, BUFFERSIZE, L"ファイルをコピーできませんでした。\n%s", src);
				MessageBox(NULL, buf, L"エラー", MB_OK);
			}
		}

	} while (FindNextFile(h, &fd));
	FindClose(h);

	return TRUE;
}

これも処理自体はほぼ同じですが、高速化のために関数を二つに分けています。
使用時に実際に呼び出すのはMyCopyDirectory関数で、_MyCopyDirectory関数は再帰呼び出しされる関数です。
_MyCopyDirectory関数では高速化のために引数のチェックを行いません。

ディレクトリを別ボリュームへ移動する

MoveFile関数はディレクトリの移動も可能ですが、同一ボリューム(ドライブ)内の移動に限られます。
別ボリュームへの移動は「ディレクトリを中身ごとコピーしてから元を削除する」という手順で実現できます。
ここでは先ほど作成した「MyCopyDirectory関数」と「MyRemoveDirectory関数」を使用します。


//ディレクトリsrcをdstに移動(またはリネーム)する
BOOL MyMoveDirectory(const WCHAR* src, const WCHAR* dst)
{
	//ファイルが指定された場合や
	//同一ボリューム内での移動は
	//MoveFile関数にまかせる
	if (MoveFile(src, dst)) {
		return TRUE;
	}

	//ディレクトリをコピーしてから削除する
	if (MyCopyDirectory(src, dst)) {
		return MyRemoveDirectory(src);
	}
	return FALSE;
}