ファイルの読み書き

ファイルの読み取り

ファイルの読み取りはReadFile関数を使用します。

BOOL ReadFile(
 HANDLE hFile,
 LPVOID lpBuffer,
 DWORD nNumberOfBytesToRead,
 LPDWORD lpNumberOfBytesRead,
 LPOVERLAPPED lpOverlapped
);
ファイルハンドルhFileからバッファlpBufferにnNumberOfBytesToReadバイト分を読み取る。
読み取ったバイト数はlpNumberOfBytesReadに格納される。
成功した場合は0以外を、失敗した場合は0を返す。

ファイルの読み書きについてはC言語標準関数の場合とほぼ同じ動作になります。

第一引数hFileCreateFile関数で読み取りアクセス(GENERIC_READ)で開いたファイルハンドルを指定します。

第二引数lpBufferは読み取ったデータを格納する変数のポインタです。
LPVOID型は「void*型」のことで、特定のデータ型に依存しないポインタ型です。

第三引数nNumberOfBytesToReadは読み取るデータの最大バイト数です。
ファイルサイズがここで指定したサイズよりも大きい場合は次回の読み取りはその続きから行われます。

第四引数lpNumberOfBytesReadは実際に読み取られたバイト数が格納されます。
次の引数lpOverlappedNULL以外を指定する場合、ここにはNULLを指定することができます。

第五引数lpOverlappedは非同期読み取りに関するデータです。
非同期のファイル操作は複雑なのでここでは説明しません。
必要ない場合はNULLを指定します。


#include <windows.h>

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static char* buf;
	static DWORD readedSize;

	HANDLE hFile;
	LARGE_INTEGER li;

	HDC hdc;
	PAINTSTRUCT ps;
	RECT rt;

	switch (message)
	{
	case WM_CREATE: //ウィンドウの作成
		//ファイルを読み取りアクセスで開く
		hFile = CreateFile(L"main.c", GENERIC_READ, 0, NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE) {
			MessageBox(NULL, L"ファイルのオープンに失敗しました。", L"エラー", MB_OK);
			break;
		}
		//ファイルサイズを取得してメモリを確保
		GetFileSizeEx(hFile, &li);
		buf = (char*)malloc(li.QuadPart);

		//ファイルを読み取る
		ReadFile(hFile, buf, li.QuadPart, &readedSize, NULL);

		//ファイルを閉じる
		CloseHandle(hFile);
		break;

	case WM_PAINT: //ウィンドウの描画
		GetClientRect(hWnd, &rt);

		hdc = BeginPaint(hWnd, &ps);
		DrawTextA(hdc, buf, readedSize, &rt, DT_WORDBREAK | DT_EXPANDTABS);

		//UTF-16LEを読む場合
		//DrawText(hdc, buf, readedSize / sizeof(WCHAR), &rt, DT_WORDBREAK | DT_EXPANDTABS);

		EndPaint(hWnd, &ps);
		break;

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

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

現在のソースコード(main.c)を開いて読み取り、ウィンドウに出力したところです。
ReadFile関数のサンプル

データはバイト単位で読み取られるので、格納する変数の型にはchar*型を使用します。
(CHAR*型やLPSTR型でも良い。内部的には同じ)

ReadFile関数は単純に「ファイルから○バイト読み取る」という関数なので、対象がテキストファイルかバイナリファイルかは区別しません。
ファイルがテキストファイルの場合、文字コードがShift_JISならばDrawTextA関数で表示できます。
DrawTextW関数はWCHAR型用で、Visual Studioではワイド文字はUTF-16LE(リトルエンディアン)で扱われます。
なので、テキストファイルがUTF-16LEならばDrawTextW関数で表示できます。
それ以外の文字コードは変換処理が必要です。

なお、今回のバッファ(変数buf)はchar*型ですが、終端にNULL文字がないので文字列としてそのまま使用することはできません。
しかしDrawText関数は出力する文字数を指定できるので、適切な長さを指定することで問題なく表示可能です。

ファイルの書き込み

ファイルの書き込みはWriteFile関数を使用します。

BOOL WriteFile(
 HANDLE hFile,
 LPCVOID lpBuffer,
 DWORD nNumberOfBytesToWrite,
 LPDWORD lpNumberOfBytesWritten,
 LPOVERLAPPED lpOverlapped
);
ファイルハンドルhFileにバッファlpBufferをnNumberOfBytesToReadバイト分書き込む。
書き込んだバイト数はlpNumberOfBytesReadに格納される。
成功した場合は0以外を、失敗した場合は0を返す。

読み取りが書き込みに変わった以外はReadFile関数と同じです。
ただし第一引数hFileは書き込みアクセス(GENERIC_WRITE)で開いたハンドルを渡す必要があります。

ファイルポインタ

SetFilePointer関数

ファイルには現在の読み書き位置があります。
ファイルの読み書きはこの現在の位置から行われます。
これをファイルポインタといいます。
(ファイルの読み書き位置の移動をシーク(seek)といいます)

ファイルを開いた時のファイルポインタはファイルの先頭にあります。
データの読み取りや書き込みを行うと、読み書きした分だけファイルポインタは移動します。
データを読み書きせずにファイルポインタを任意の位置に移動するにはSetFilePointer関数を使用します。

DWORD SetFilePointer(
 HANDLE hFile,
 LONG lDistanceToMove,
 PLONG lpDistanceToMoveHigh,
 DWORD dwMoveMethod
);
ファイルハンドルhFileのファイルポインタを、基準位置dwMoveMethodから下位ダブルワードlDistanceToMoveと上位ダブルワードlpDistanceToMoveHighで指定する位置に設定する。
成功した場合は新しいファイルポインタの下位ダブルワードを返し、上位ダブルワードをlpDistanceToMoveHighに格納する。
失敗した場合はINVALID_SET_FILE_POINTERを返す。

第一引数hFileCreateFile関数で開いたファイルのハンドルです。
ただしパイプや通信デバイスなどはシークできないので操作できません。
(関数はエラーを返さないかもしれないが、未定義の動作になる)

第二引数lDistanceToMoveはファイルポインタを指定する値の下位ダブルワードです。
第三引数lpDistanceToMoveHighはファイルポインタの上位ダブルワードです。
(第三引数はポインタを指定する必要があるので注意)
ファイルポインタはダブルワード(32bit整数)を二つ使用して64bit整数で表されます。
上位ダブルワードにはNULLを指定することもでき、その場合は下位ダブルワードのみで約2GBまでのシークが可能です。
(2の31乗 - 2バイト。負数の指定もあるため32乗ではない)
ファイルポインタの指定は負数(マイナス)を使用することもでき、その場合は基準位置から前方へシークされます。

第四引数dwMoveMethodはシークの基準となる位置の指定です。
第二、第三引数の値はこの位置から後方(プラス値)または前方(マイナス値)にシークします。
以下の定数のいずれかを指定します。

定数 説明
FILE_BEGIN ファイルの先頭位置
FILE_CURRENT 現在の位置
FILE_END ファイルの終端位置

戻り値は新しく設定されたファイルポインタの下位ダブルワードです。
第三引数のlpDistanceToMoveHighNULL以外を指定した場合、ここに指定した変数には新しいファイルポインタの上位ダブルワードが格納されます。

関数が失敗した場合はINVALID_SET_FILE_POINTERという定数を返します。
ただしこの定数の定義と同じ値が有効なファイルポインタとして返される場合があります。
INVALID_SET_FILE_POINTERが返ってきた場合、GetLastError関数を使用し、戻り値がNO_ERROR以外の場合は関数は失敗しています。

新しく設定されるファイルポインタが負数になる場合は関数は失敗します。
(ファイルの先頭位置よりも前方に設定される場合)
第三引数のlpDistanceToMoveHighNULLを指定し、新しく設定されるファイルポインタが32bitで表される値を超える場合も関数は失敗します。

新しく設定されるファイルポインタが現在のファイル終端位置を超えていても関数は成功します。
ただしその位置に(WriteFile関数やSetEndOfFile関数などで)データを書き込むまではファイルサイズは増加しません。
ファイル終端を超えてデータを書き込んだ場合、ファイル終端位置から新しく設定されるファイルポインタまでの間のデータは初期化されません。
(何が入っているかは不定)

SetFilePointerEx関数

ファイルポインタの設定はSetFilePointerEx関数を使用することもできます。

BOOL SetFilePointerEx(
 HANDLE hFile,
 LARGE_INTEGER liDistanceToMove,
 PLARGE_INTEGER lpNewFilePointer,
 DWORD dwMoveMethod
);
ファイルハンドルhFileのファイルポインタを、基準位置dwMoveMethodからliDistanceToMoveで指定する位置に設定し、新しいファイルポインタをlpNewFilePointerに格納する。
成功した場合は0以外を、失敗した場合は0を返す。

ファイルポインタを64bit整数で扱えること以外はSetFilePointer関数と同じです。
64bit整数を扱うLARGE_INTEGER共用体についてはGetFileSizeEx関数を参照してください。

LARGE_INTEGER共用体という見慣れないものを使用する必要はありますが、特別難しいものではありませんし関数の扱いはこちらの方が簡単です。
第三引数はポインタを指定する必要があるので注意してください。

SetEndOfFile関数

SetEndOfFile関数は、ファイルの終端を現在のファイルポインタに設定します。

BOOL SetEndOfFile(
 HANDLE hFile
);
ファイルハンドルhFileのファイル終端(EOF)を現在のファイルポインタに設定する。
成功した場合は0以外を、失敗した場合は0を返す。

SetFilePointer関数で任意の位置にファイルポインタを設定し、この関数を実行することでファイルを切り捨てたりファイルサイズを拡張することができます。
ファイルハンドルには書き込みアクセス(GENERIC_WRITE)が必要です。