【C++】指定パス以下のファイルを全て取得する

# はじめに
指定パス以下にある全てのファイルを取得したいことがあったので調べてみました．

(2020/01/19 追記)
コメントで頂いたrecursive_directory_iteratorについて追記しました．

# 実行環境
- C++
- Windows10
- VisualStudio / VSCode
- 非Unicodeビルド環境(後述)

# コード
結論から言えば下記のコードです．

```c++:file.cpp
#include <Windows.h>
#include <string>
#include <vector>

std::vector<std::string> scan_directory(const std::string& dir_name) {

	HANDLE fHandle;
	WIN32_FIND_DATA win32fd;
	std::vector<std::string> file_names;

	std::string search_name = dir_name + "\\*";

    fHandle = FindFirstFile(search_name.c_str(), &win32fd);

	if (fHandle == INVALID_HANDLE_VALUE) {
        // "'GetLastError() == 3' is 'file not found'"
        return file_names;
	}

	do {
		if (win32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {//ディレクトリである

			//そのディレクトリそのものを表す場合は処理しない
			if (win32fd.cFileName[0] == '.') continue;

			std::string fullpath = dir_name + "\\" + win32fd.cFileName;

			//再帰的にファイルを検索
			std::vector<std::string> files = scan_directory(fullpath);

			file_names.insert(file_names.end(), files.begin(), files.end());
		}
		else {//ファイルである
			std::string fullpath = dir_name + "\\" + win32fd.cFileName;
			file_names.emplace_back(fullpath);
		}
	} while (FindNextFile(fHandle, &win32fd));

	FindClose(fHandle);

	return file_names;
}
```

# ちょっと解説
Win32APIの`FindFirstFile`および`FindNextFile`を使います．
`dwFileAttributes`と`FILE_ATTRIBUTE_DIRECTORY`のビット論理積で，取得したパスがディレクトリであるかどうかの判定ができます．
ディレクトリであればフルパスを生成して再帰的に検索するように組んでみました．

ちなみに，あるディレクトリ`DIR`を指定して検索すると`DIR\.`とか`DIR\..`も取得できてしまうので，それは処理しないようにしています．
最後に`FindClose`で閉じましょう．

# 文字コードセットに関して
Win32APIで文字列を扱うとき(特に日本語が含まれる場合)，文字セットの設定を忘れると時々エラーに陥ります．
Win32APIはビルド時の文字セット設定によって内部の文字列型を使い分けています．
おそらく基本的には以下の通り．

- Unicodeビルド環境…　`const wchar_t*`型
- 非Unicodeビルド環境…`const char*`型

上記のコードは`std::string`型を使用しており，これは`const char*`に変換されるため，非Unicodeビルド環境でないとエラーになる可能性があります．
Unicodeビルド環境を使うなら`std::wstring`を使うか適切な変換が必要です．

#### 文字セットを変更する
ビルド時の文字セット設定の変更方法について，書いておきます．

##### VisualStudio(2017)

プロジェクト > 〇〇のプロパティ > 全般 > 文字セット
から変更できます．

##### VSCode

View > Command Palette > C/C++ Edit Configurations(JSON)
を開いて，
"configurations" > "defines"
のところに"UNICODE"を追加/削除することで変更できます．

プログラム中にパスを直で書く場合，VSCodeではエディタの文字コードも影響します．
フッター部分にある select encodingから変更できます．

# recursive_directory_iteratorについて
(2020/01/19追記)
C++17からは標準で`std::filesystem`が使えます．
`std::filesystem::recursive_directory_iterator`であればWin32APIなど使わずに簡潔に書けます．
[filesystem -cpprefjp C++日本語リファレンス-](https://cpprefjp.github.io/reference/filesystem.html)

```c++:file.cpp
#include <filesystem>
#include <string>
#include <vector>

std::vector<std::string> get_file_paths(const std::string& dir_name) {

	std::vector<std::string> file_paths;

	for (const std::filesystem::directory_entry& de : std::filesystem::recursive_directory_iterator(dir_name)) {
		file_paths.emplace_back(de.path().string());
	}

	return file_paths;
}

```

`recursive_directory_iterator`クラスは指定したパス以下を再帰的に走査します．
その時取得した要素は`directory_entry`クラスで表されます．

# おまけ
このコードを使ってちょっとしたツール作ってみました．
インターネットショートカット(.url)をまとめたディレクトリがあるとき，そのパスを指定してGoogle Chromeの別ウィンドウとして一気に開きます．

```c++:openURL.cpp
#include <string>
#include <vector>
#include <fstream>
#include "file.h"
using namespace std;

int main(int argc, char *argv[]) {
    
    if (argc!=2) return 0;
    string dir_path = argv[1];//フォルダをドラッグ&ドロップすると第2引数になる

    vector<string> files = scan_directory(dir_path);

    for (int i = 0; i < files.size();i++) {

        if (files[i].find(".url") == string::npos) continue;

        ifstream ifs(files[i]);
        string tag,url;
        ifs >> tag >> url;
        ifs.close();

        string cmd;
		if (i==0) cmd = "start chrome \"--new-window " + url.substr(4) + "\"";
		else cmd = "start chrome \"" + url.substr(4) + "\"";

        system(cmd.c_str());

        if (i==0) Sleep(100);
    }

    return file_names; 
}
```

# 参考ページ
[Win32API ディレクトリを走査しファイルを検索する -HatenaBlog-](http://s-kita.hatenablog.com/entry/20100129/1264776052)
[紛らわしいぞ！LPCTSTR、LPTSTR、LPSTR、LPCSTRは全部意味が違う！ -UsefullCode.net-](https://www.usefullcode.net/2006/11/lpctstrlptstrlpstrlpcstr.html)