ファイル操作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に分けて、別々に取得します。
そのため後で結果を結合する必要がありやや面倒です。
上位ダブルワードが必要ない場合はlpFileSizeHigh
にNULL
を指定することができます。
この場合取得できるのは32bit分(約4GB)までとなります。
戻り値はファイルサイズの下位ダブルワードです。
関数が失敗した場合はINVALID_FILE_SIZE
という定数が返されるのですが、lpFileSizeHigh
にNULL
以外を指定した場合は正常終了時にもこの定数と同じ値が返されることがあります。
(INVALID_FILE_SIZE=0xFFFFFFFF=4,294,967,295。ファイルサイズがこれ以上となる場合)
なので、関数の成否の判定にはGetLastError
関数を使用します。
この関数の戻り値がNO_ERROR
以外のとき、関数は失敗しています。
lpFileSizeHigh
にNULL
を指定した場合は関数の戻り値が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を返す。
第二引数のlpFileSize
はLARGE_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番目のファイル時間よりも遅い |