ファイル操作1

ファイルの作成/ハンドルの取得

ファイルの作成はCreateFile関数を使用します。

HANDLE CreateFileW(
 LPCWSTR lpFileName,
 DWORD dwDesiredAccess,
 DWORD dwShareMode,
 LPSECURITY_ATTRIBUTES lpSecurityAttributes,
 DWORD dwCreationDisposition,
 DWORD dwFlagsAndAttributes,
 HANDLE hTemplateFile
);
ファイルlpFileNameを新規作成する。
または既存のファイルのハンドルを取得する。
成功した場合はハンドルを返す。
失敗した場合は定数INVALID_HANDLE_VALUEを返す。

「Create」となっていますが、ファイルの新規作成のほか既存のファイルを操作するためのハンドルを取得するときにも使用します。
また、ファイル以外にもコンソールや通信デバイスなどの操作も可能です。

lpFileNameは作成またはハンドルを取得するファイルのパス文字列です。
パスの区切り文字は「\」(円記号またはバックスラッシュ)のほか「/」(スラッシュ)を使用することができます。
相対パス/絶対パスのどちらでも指定可能です。
..\はひとつ上のフォルダ階層を表します。

Windowsで扱えるパス名の長さの最大はMAX_PATHという定数で定義されています。
(260文字)
Windows10バージョン1607以降はこの制限が緩和されていて、ファイルパスを扱う関数ではパス名の前に\\?\を付加すると32767文字まで指定可能になります。


//Cドライブ直下に「test.txt」ファイルを作る場合
//エスケープシーケンスに注意
HANDLE h = CreateFile(L"\\\\?\\C:\\test.txt", /*省略*/);

//スラッシュを使用する場合
HANDLE h = CreateFile(L"//?/C:/test.txt", /*省略*/);

ただしANSI版の関数(CreateFileA関数)ではできません。
これは他のファイルパスを扱うANSI版の関数でも同様です。

dwDesiredAccessはファイルへのアクセス方法です。
以下の値を指定します。

説明
0 オブジェクト(操作の対象)のデバイス属性の問い合わせ
GENERIC_READ 読み取りアクセス
GENERIC_READ | GENERIC_WRITEを指定することで読み書きアクセスの指定
GENERIC_WRITE 書き込みアクセス
GENERIC_READ | GENERIC_WRITEを指定することで読み書きアクセスの指定

dwShareModeはファイルの共有モードの指定です。
プログラムがファイルをオープンしている間、他のプログラムからの同ファイルへのアクセス許可を以下の値から指定します。

説明
0 共有を許可しない
他のプログラムからのオープンは失敗する
FILE_SHARE_READ 読み取りを許可
FILE_SHARE_READ | FILE_SHARE_WRITEを指定することで読み書きアクセスを許可
FILE_SHARE_WRITE 書き込みを許可
FILE_SHARE_READ | FILE_SHARE_WRITEを指定することで読み書きアクセスを許可
FILE_SHARE_DELETE 削除および名前変更の許可

lpSecurityAttributesセキュリティ記述子を設定するSECURITY_ATTRIBUTES構造体の指定です。
セキュリティ記述子とはファイルの所有者やアクセス権者を設定するものです。
NULLを指定すると既定のセキュリティ記述子を使用します。
(ややこしいので説明は省きます)

dwCreationDispositionはファイルの存在状態による動作を以下の定数のいずれかで指定します。

説明
CREATE_NEW ファイルが存在しない場合に新規作成する
ファイルが存在する場合は関数は失敗する
CREATE_ALWAYS 常にファイルを新規作成する
すでにファイルが存在する場合は上書きする
OPEN_EXISTING ファイルが存在する場合に開く
ファイルが存在しない場合は関数は失敗する
OPEN_ALWAYS 常にファイルを開く
ファイルが存在しない場合は新規作成した上で開く
TRUNCATE_EXISTING ファイルを開きサイズを0に切り詰める
ファイルが存在しない場合は関数は失敗する
(書き込みアクセスが必要)

dwFlagsAndAttributesはファイルまたはデバイスの属性の指定です。
FILE_ATTRIBUTE_NORMALは通常のファイルです。
FILE_ATTRIBUTE_READONLYは読み取り専用ファイルです。
FILE_ATTRIBUTE_HIDDENは隠しファイルです。
この引数に指定できるフラグは非常に数が多くややこしいのこれ以上の説明は省きます。

hTemplateFileは、ファイル属性の元となるファイルのパスの指定です。
ファイルを新規作成する時、ここで指定したファイルと同じ属性でファイルを作成します。
必要ない場合はNULLを指定できます。

戻り値は操作するファイルのハンドルです。
関数が失敗した場合はINVALID_HANDLE_VALUEという定数です。
(0ではありません)

ファイルのクローズ

CreateFile関数で開いたファイルを閉じるにはCloseHandle関数を使用します。

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

開いたファイルは閉じる、というのはC言語のファイル操作関数と同じです。


WCHAR *filePath = L"C:/test.txt";

//ファイルを読み取り専用で開く
//存在しない場合は関数は失敗する
HANDLE h = CreateFile(filePath, GENERIC_READ, 0, NULL,
				OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {//成功
	//何らかのファイル操作

	//ハンドルを閉じる
	CloseHandle(h);
}

ファイルの情報

ファイルサイズ

GetFileSize関数

ファイルサイズの取得はGetFileSize関数を使用します。

DWORD GetFileSize(
 HANDLE hFile,
 LPDWORD lpFileSizeHigh
);
ファイルハンドルhFileのサイズの上位ダブルワードをlpFileSizeHighに格納し、サイズの下位ダブルワードを返す。

ファイルサイズは64bit整数値で表されます。
(WORD=16bit、DWORD=32bit)
この関数はサイズを上位32bitと下位32bitに分けて、別々に取得します。
そのため後で結果を結合する必要がありやや面倒です。

上位ダブルワードが必要ない場合はlpFileSizeHighNULLを指定することができます。
この場合取得できるのは32bit分(約4GB)までとなります。

戻り値はファイルサイズの下位ダブルワードです。
関数が失敗した場合はINVALID_FILE_SIZEという定数が返されるのですが、lpFileSizeHighNULL以外を指定した場合は正常終了時にもこの定数と同じ値が返されることがあります。
(INVALID_FILE_SIZE=0xFFFFFFFF=4,294,967,295。ファイルサイズがこれ以上となる場合)
なので、関数の成否の判定にはGetLastError関数を使用します。
この関数の戻り値がNO_ERROR以外のとき、関数は失敗しています。

lpFileSizeHighNULLを指定した場合は関数の戻り値がINVALID_FILE_SIZEか否かをチェックするだけで大丈夫です。


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

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

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

#define IDC_STATIC -1
#define IDC_EDIT1 100
#define IDC_BUTTON1 101
#define IDC_BUTTON2 102

//ファイルfilePathのサイズを取得しhWndに出力する
BOOL DrawFileSize(const WCHAR *filePath, HWND hWnd)
{
	//ファイルを読み取り専用で開く
	HANDLE h = CreateFile(filePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
	
	//ファイルサイズの取得
	DWORD low, high;
	low = GetFileSize(h, &high);
	if (low == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) {
		CloseHandle(h);
		return FALSE;
	}
	//ハンドルを閉じる
	CloseHandle(h);

	//下位DWORDと上位DWORDを結合
	LONGLONG size = low + ((LONGLONG)high << 32);

	//数値を文字列に変換
	WCHAR buf1[21];
	StringCchPrintf(buf1, 21, L"%lld", size);

	//カンマ区切り文字列に変換
	WCHAR buf2[27];		//変換後の文字列を格納するバッファ
	NUMBERFMT nf = {0};	//文字列整形のための構造体
	nf.Grouping = 3;	//3桁区切り
	nf.lpDecimalSep = L".";	//小数点の文字
	nf.lpThousandSep = L",";//区切り文字
	//ロケール(日本), 0, 整形する文字列, NUMBERFMT構造体, バッファ, バッファサイズ
	GetNumberFormatEx(L"ja_JP", 0, buf1, &nf, buf2, 27);
	
	//最終的な文字列の出力を作成
	WCHAR txt[30];
	StringCchPrintf(txt, 30, L"%sバイト", buf2);

	SetWindowText(hWnd, txt);
	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hEdit, hStatic;
	//MAX_PATHはWindowsで使用可能なパスの最大長を表す定数
	static WCHAR filePath[MAX_PATH];
	static OPENFILENAME ofn;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		hEdit = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"EDIT", L"",
			WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_BORDER,
			10, 10, 440, 24,
			hWnd, (HMENU)IDC_EDIT1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"...",
			WS_CHILD | WS_VISIBLE,
			450, 10, 40, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);

		CreateWindow(
			L"BUTTON", L"サイズ取得",
			WS_CHILD | WS_VISIBLE,
			10, 35, 120, 24,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);
		hStatic = CreateWindow(
			L"STATIC", NULL,
			WS_CHILD | WS_VISIBLE,
			130, 35, 360, 24,
			hWnd, (HMENU)IDC_STATIC, hInst, NULL);

		//ファイルの選択ダイアログの初期化
		ofn.lStructSize = sizeof(OPENFILENAME);
		ofn.hwndOwner = hWnd;
		ofn.lpstrFilter = L"全てのファイル (*.*)\0*.*\0";
		ofn.nFilterIndex = 0;
		ofn.lpstrFile = filePath;
		ofn.nMaxFile = MAX_PATH;
		ofn.Flags = OFN_FILEMUSTEXIST;
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1: //「...」ボタン
			filePath[0] = '\0';
			//GetOpenFileName関数が成功すると
			//filePathに選択したファイルのパスが格納される
			if (GetOpenFileName(&ofn)) {
				SetWindowText(hEdit, filePath);
				if (!DrawFileSize(filePath, hStatic)) {
					SetWindowText(hStatic, L"失敗");
				}
			}
			break;
			
		case IDC_BUTTON2: //「サイズ取得」ボタン
			GetWindowText(hEdit, filePath, MAX_PATH);
			if (!DrawFileSize(filePath, hStatic)) {
				SetWindowText(hStatic, L"失敗");
			}
			break;
		}
		break;

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

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

選択したファイルのサイズを表示するサンプルです。
「...」ボタンでファイル選択ダイアログが開き、選択したファイルのサイズを取得します。
エディットコントロールにファイルパスを直接入力し、「サイズ取得」ボタンでファイルサイズを取得することもできます。
ファイルサイズの取得

ファイルサイズの数字(文字列データ)を3桁ごとにカンマ区切りするためにGetNumberFormatExという関数を使用していますが、ここでは説明は省きます。
(コメントを参照してください)

コード中で使用しているOPENFILENAME構造体、およびGetOpenFileName関数は、既存のファイルを選択するダイアログを開くためのものです。
詳しくはファイル選択ダイアログの項で改めて説明します。

GetFileSizeEx関数

ファイルサイズはGetFileSizeEx関数でも取得することができます。

BOOL GetFileSizeEx(
 HANDLE hFile,
 PLARGE_INTEGER lpFileSize
);
ファイルハンドルhFileのサイズを取得しLARGE_INTEGER共用体変数lpFileSizeに格納する。
成功した場合は0以外を、失敗した場合は0を返す。

第二引数のlpFileSizeLARGE_INTEGER共用体の変数へのポインタです。

typedef union _LARGE_INTEGER {
 struct {
  DWORD LowPart;
  LONG HighPart;
 } DUMMYSTRUCTNAME;
 struct {
  DWORD LowPart;
  LONG HighPart;
 } u;
 LONGLONG QuadPart;
} LARGE_INTEGER;
64bit符号付き整数を格納する共用体。

LARGE_INTEGERは共用体です。
64bit整数値がそのまま扱えない環境用に32bitずつに分けてデータが格納されます。
最近の一般的な環境では64bit整数をそのまま扱えるので、64bit整数型であるQuadPartメンバをそのまま使用するだけで良いです。
GetFileSize関数とは違い、結果を結合する手間がないのでこちらの方が手軽です。

先ほどのサンプルコードのファイルサイズ取得部分をGetFileSizeEx関数で書き換えると以下のようになります。


//ファイルを開く
HANDLE h = CreateFile(/*省略*/);
if (h == INVALID_HANDLE_VALUE) {
	return FALSE;
}

//ファイルサイズの取得
LARGE_INTEGER li;
if (!GetFileSizeEx(h, &li)) {
	CloseHandle(h);
	return FALSE;
}
//ハンドルを閉じる
CloseHandle(h);

//数値を文字列に変換
//QuadPartメンバは64bit整数
WCHAR buf1[21];
StringCchPrintf(buf1, 21, L"%lld", li.QuadPart);

ファイルの種類

ファイルの種類の取得はGetFileType関数を使用します。

DWORD GetFileType(
 HANDLE hFile
);
ファイルハンドルhFileの種類を取得して返す。

戻り値は以下の定数のいずれかです。

定数 説明
FILE_TYPE_UNKNOWN 不明なファイルタイプ
または関数の失敗
FILE_TYPE_DISK ディスクファイル
(通常のファイル)
FILE_TYPE_CHAR 文字ファイル
通常はLPTデバイス(プリンタポート)またはコンソール
FILE_TYPE_PIPE ソケット、名前付きパイプ、名前なしパイプ
FILE_TYPE_REMOTE 未使用

通常のファイルを開いた場合、戻り値はFILE_TYPE_DISKです。

日時情報

GetFileTime関数

ファイルには「作成日時」「更新日時」「最終アクセス日時」などの情報があります。
これらの日時情報を取得するにはGetFileTime関数を使用します。

BOOL GetFileTime(
 HANDLE hFile,
 LPFILETIME lpCreationTime,
 LPFILETIME lpLastAccessTime,
 LPFILETIME lpLastWriteTime
);
ファイルハンドルhFileの日情報を取得する。
成功した場合は0以外を、失敗した場合は0を返す。

lpCreationTimeは作成日時を表します。
lpLastAccessTimeは最終アクセス日時を表します。
lpLastWriteTimeは更新日時を表します。
取得する必要のない項目にはNULLを指定することもできます。

これらの日時情報の格納にはFILETIME構造体を使用します。

typedef struct _FILETIME {
 DWORD dwLowDateTime;
 DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
ファイル時間を格納する構造体。

日時情報は64bit整数で扱われており、上位ダブルワードと下位ダブルワードに分けて格納されています。
この構造体はFileTimeToSystemTime関数でSYSTEMTIME構造体に変換することができます。
(→日時と時間計測)

BOOL FileTimeToSystemTime(
 const FILETIME *lpFileTime,
 LPSYSTEMTIME lpSystemTime
);
FILETIME構造体lpFileTimeをSYSTEMTIME構造体lpSystemTimeに変換する。
成功した場合は0以外を、失敗した場合は0を返す。

ただしGetFileTime関数で取得した時点ではUTC時間なので、FileTimeToSystemTime関数の実行前にFileTimeToLocalFileTime関数でローカル時間に変換しておきます。

BOOL FileTimeToLocalFileTime(
 const FILETIME *lpFileTime,
 LPFILETIME lpLocalFileTime
);
UTC時間を格納するFILETIME構造体lpFileTimeをローカル時間に変換しFILETIME構造体lpLocalFileTimeに変換する。
成功した場合は0以外を、失敗した場合は0を返す。

ファイル日時を取得する関数のサンプルです。


//ファイルfilePathの作成日時を取得しhWndに出力する
BOOL DrawFileTime(const WCHAR* filePath, HWND hWnd)
{
	//ファイルを読み取り専用で開く
	HANDLE h = CreateFile(filePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	//ファイルの作成日時を取得する
	FILETIME ftCreation;
	if (!GetFileTime(h, &ftCreation, NULL, NULL)) {
		CloseHandle(h);
		return FALSE;
	}
	//ハンドルを閉じる
	CloseHandle(h);

	//ローカル時間に変換
	FILETIME ftCreationLocal;	
	FileTimeToLocalFileTime(&ftCreation, &ftCreationLocal);

	//SYSTEMTIME構造体に変換
	SYSTEMTIME st;
	FileTimeToSystemTime(&ftCreationLocal, &st);

	//日時を文字列に変換
	WCHAR txt[20];
	StringCchPrintf(txt, 20,
		L"%04d/%02d/%02d "
		L"%02d:%02d:%02d",
		st.wYear, st.wMonth, st.wDay,
		st.wHour, st.wMinute, st.wSecond);

	SetWindowText(hWnd, txt);
	return TRUE;
}

SetFileTime関数

ファイルに日時情報をセットするにはSetFileTime関数を使用します。

BOOL SetFileTime(
 HANDLE hFile,
 const FILETIME *lpCreationTime,
 const FILETIME *lpLastAccessTime,
 const FILETIME *lpLastWriteTime
);
ファイルハンドルhFileの日情報を設定する。
成功した場合は0以外を、失敗した場合は0を返す。

ファイルは書き込み権限(GENERIC_WRITE)でオープンする必要があります。
書き換える必要のない日時情報にはNULLをセットすることができます。

日時情報をセットしたFILETIME構造体を作成するにはSystemTimeToFileTime関数を使用します。

BOOL SystemTimeToFileTime(
 const SYSTEMTIME *lpSystemTime,
 LPFILETIME lpFileTime
);
SYSTEMTIME構造体lpSystemTimeをFILETIME構造体lpFileTimeをに変換する。
成功した場合は0以外を、失敗した場合は0を返す。

ファイルのローカル時間をUTC時間に変換するにはLocalFileTimeToFileTime関数を使用します。

BOOL LocalFileTimeToFileTime(
 const FILETIME *lpLocalFileTime,
 LPFILETIME lpFileTime
);
ローカル時間を格納するFILETIME構造体lpLocalFileTimeをUTC時間に変換しFILETIME構造体lpFileTimeに格納する。
成功した場合は0以外を、失敗した場合は0を返す。

ただしファイルに時間を設定する場合はローカル時間をそのまま指定しても問題ありません。

以下はファイルの作成日時を設定する自作関数の例です。
引数のSYSTEMTIME構造体には任意の日時をセットして渡します。


//ファイルfilePathの作成日時をセットする
BOOL MySetFileTime(const WCHAR* filePath, SYSTEMTIME st)
{
	//ファイルを書き込み権限で開く
	HANDLE h = CreateFile(filePath, GENERIC_WRITE, 0, NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (h == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	//SYSTEMTIME構造体をFILETIME構造体に変換
	FILETIME ft;
	SystemTimeToFileTime(&st, &ft);

	//ファイル時間を設定
	BOOL r = SetFileTime(h, &ft, NULL, NULL);
	
	//ハンドルを閉じる
	CloseHandle(h);

	return r;
}

CompareFileTime関数

ファイル日時の比較はCompareFileTime関数で行います。

LONG CompareFileTime(
 const FILETIME *lpFileTime1,
 const FILETIME *lpFileTime2
);
二つのファイル時間を比較する。

戻り値は以下です。

戻り値 説明
-1 1番目のファイル時間は2番目のファイル時間よりも早い
0 二つのファイル時間は同じ
1 1番目のファイル時間は2番目のファイル時間よりも遅い