ファイル読み書き


ファイルを読みこむ

Win32 API を使ってファイルの読み書きを行うことができます
基本的な概念は C 言語と同じで、ファイルポインタを対象に読み書きを行い
その後、ファイルポインタは読み取ったバイト数だけ調整されるというものです

ファイルを読みこむには ReadFile() 関数を使用します
この関数は、現在のファイルポインタから指定バイト数だけバッファに読みこみます
BOOL ReadFile(
	HANDLE hFile ,
	LPVOID lpBuffer ,
	DWORD nNumberOfBytesToRead ,
	LPDWORD lpNumberOfBytesRead ,
	LPOVERLAPPED lpOverlapped
);
hFile には読みこむ GENERIC_READ アクセスを持つファイルのハンドルを
lpBuffer はファイルから読み込んだデータを格納するバッファへのポインタ
nNumberOfBytesToRead は読み取るバイト数を指定します

lpNumberOfBytesRead は DWORD 型へのポインタを指定します
このポインタに、実際に読みこんだバイト数が格納されます
lpOverlapped を NULL に指定した場合は、必ず指定する必要があります
lpOverlapped は、OVERLAPPPED 構造体へのポインタを指定します
関数が成功すると 0 以外、失敗すると 0 が返ります

lpOverlapped には hFile が FILE_FLAG_OVERLAPPED フラグを持つ場合
必ず指定しなければなりません (それ以外の場合、通常は NULL を指定します)
なぜならば、このフラグを指定するとファイルポインタの保守をシステムは行いません
OVERLAPPED 構造体の情報を使って、ファイルの読み書きを行うのです
typedef struct _OVERLAPPED {
    DWORD  Internal; 
    DWORD  InternalHigh; 
    DWORD  Offset; 
    DWORD  OffsetHigh; 
    HANDLE hEvent; 
} OVERLAPPED;
Internal と InternalHigh メンバは、OS が使用するメンバです
Offset は読み取りを開始するファイル位置の下位 32 ビット、OffsetHigh が上位 32 ビット
hEvent には転送終了時にシグナル状態に設定するイベントのハンドルを設定します
hEnvet は ReadFile()、WriteFile()、ConnectNamedPipe()、そして TransactNamedPipe() を
呼び出す前に設定しておく必要があります

FILE_FLAG_OVERLAPPED フラグを持つファイルを ReadFile() 関数で読み取ると
有効な OVERLAPPED 構造体へのポインタを持つ場合は非同期に読み込みます
そのため、関数の制御が戻ってもファイルが読み込まれているとは限りません
その場合も ReadFile() 関数は 0 を返します
#include <windows.h>

HANDLE hFile;

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	static DWORD wReadSize;
	static RECT rect;
	static LPSTR strFile;

	switch (msg) {
	case WM_DESTROY:
		free(strFile);
		CloseHandle(hFile);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		strFile = (LPSTR)malloc(GetFileSize(hFile , NULL));
		ReadFile(hFile , strFile , GetFileSize(hFile , NULL) , &wReadSize , NULL);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		GetClientRect(hwnd , &rect);
		DrawText(hdc , strFile , wReadSize , &rect , DT_LEFT);
		EndPaint(hwnd , &ps);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
		PSTR lpCmdLine , int nCmdShow) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	hFile = CreateFile(lpCmdLine , GENERIC_READ , 0 , NULL ,
		OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL , TEXT("ファイルが開けません") , NULL , MB_OK);
		return -1;
	}

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}


コマンドライン引数から得たファイル名のファイルを開き
その内容をクライアント領域に描画するというプログラムです


ファイルへの書き込み

同様に、データをファイルに書き込むことも可能です
ファイルに書き込みを行うには WriteFile() 関数を用います
BOOL WriteFile(
	HANDLE hFile ,
	LPCVOID lpBuffer ,
	DWORD nNumberOfBytesToWrite ,
	LPDWORD lpNumberOfBytesWritten ,
	LPOVERLAPPED lpOverlapped
);
hFile には GENERIC_WRITE アクセスを持つファイルハンドルを
lpBuffer には書き込むデータが格納されているバッファへのポインタ
nNumberOfBytesToWrite には書き込むバイト数を指定します

lpNumberOfBytesWritten は DWORD へのポインタを、
lpOverlapped は OVERLAPPED 構造体へのポインタを指定します
これらの仕様は ReadFile() 関数のものと同じです
関数が成功すると 0 以外、失敗すると 0 が返ります
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
		PSTR lpCmdLine , int nCmdShow) {
	int param2;
	HANDLE hFile;
	DWORD dwWriteSize;
	PSTR lpCmdLine2;

	for (param2 = 0 ; *(lpCmdLine + param2) ; param2++)
		if (*(lpCmdLine + param2) == ' ') break;

	*(lpCmdLine + param2) = 0;
	lpCmdLine2 = lpCmdLine + param2 + 1;

	hFile = CreateFile(lpCmdLine , GENERIC_WRITE , 0 , NULL ,
		CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL , TEXT("ファイルが開けません") , NULL , MB_OK);
		return 1;
	}

	WriteFile(hFile , lpCmdLine2 , lstrlen(lpCmdLine2) , &dwWriteSize , NULL);
	CloseHandle(hFile);

	return 0;
}
このプログラムは、コマンドライン引数の第一引数のファイルを作成、または開き
第二引数で指定した文字列を書き込むプログラムです
引数はスペース ' ' で判断しますので、正しく入力してください(エラー未処理)


ファイルポインタとEOF

これまで、ファイルの先頭をファイルポインタとしてファイルを操作しましたが
ファイルの途中から読み書きをするという場合、これだけではできないので
ファイルポインタを指定位置に明示的に移動させる必要が生じます
ファイルポインタを移動させるには SetFilePointer() 関数を使います
DWORD SetFilePointer(
	HANDLE hFile ,
	LONG lDistanceToMove ,
	PLONG lpDistanceToMoveHigh ,
	DWORD dwMoveMethod
);
hFile は GENERIC_READ または GENERIC_WRITE アクセスを持つファイルハンドルを
lDistanceToMove にはオフセット指定の新しいファイルポインタの位置を指定します
正の値は前身させ、負の値は後退させることになります

lpDistanceToMoveHigh は、オフセットの上位 32 ビットが格納されているポインタを渡します
関数が成功するとこの変数に新しいファイルポインタ位置の上位 32 ビットが格納されます
不必要な場合は NULL を指定してもかまいません
これに有効なポインタを指定すると (2 ^ 64) - 2 バイトまでのファイルが操作できますが
NULL を指定すると (2 ^ 32) - 2 バイトまでのファイルしか操作できません

dwMoveMethod はファイルポインタの移動開始地点を定数で指定します
次のいずれかを指定してください

定数解説
FILE_BEGIN ファイルの先頭を開始点にします
FILE_CURRENT ファイルポインタの現在位置を開始点にします
FILE_END ファイルの終端を開始点にします

関数が成功すれば、新しいファイルポインタの下位 32 ビットが返ります
関数が失敗したならば 0xFFFFFFFF が返ります
通信デバイスなどのシーク機構を持たないデバイスにこの関数は使えません

ファイルを拡張、または縮小するには SetEndOfFile() 関数を使います
この関数は EOF を現在のファイルポインタに移動させます
拡張した場合、拡張部分のデータは未定義です

BOOL SetEndOfFile(HANDLE hFile);

hFile には、GENERIC_WRITE アクセスを持つファイルハンドルを指定します
関数が成功すれば 0 以外、失敗すれば 0 が返ります
この関数は、書き込むのではなく単純にファイルサイズを変更したい時に便利です
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
		PSTR lpCmdLine , int nCmdShow) {
	int param2;
	HANDLE hFile;
	DWORD dwWriteSize;
	PSTR lpCmdLine2;

	for (param2 = 0 ; *(lpCmdLine + param2) ; param2++)
		if (*(lpCmdLine + param2) == ' ') break;

	*(lpCmdLine + param2) = 0;
	lpCmdLine2 = lpCmdLine + param2 + 1;

	hFile = CreateFile(lpCmdLine , GENERIC_READ | GENERIC_WRITE , 0 , NULL ,
		OPEN_ALWAYS , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL , TEXT("ファイルが開けません") , NULL , MB_OK);
		return 1;
	}

	SetFilePointer(hFile , 0 , NULL , FILE_END);
	
	WriteFile(hFile , lpCmdLine2 , lstrlen(lpCmdLine2) , &dwWriteSize , NULL);
	CloseHandle(hFile);

	return 0;
}
このプログラムは、先ほどのプログラムと同じものですが
既存のファイルを選択した場合、その終端から指定文字を書き込みます
これは、ファイルをオープンすると同時に EOF にファイルポインタを移動させているためです


フラッシュ

C言語のファイルシステムを学習した時に、バッファとフラッシュの概念を勉強したでしょう
Win32 API でも同様に、バッファをクリアするにはフラッシュする必要があります

バッファをクリアして、内容をファイルに書き込むには FlushFileBuffers() 関数を使います
ただし、フラッシュしなくても OS は定期的にバッファをファイルに書き込みます
明示的にバッファをフラッシュしたい場合に使用してください

BOOL FlushFileBuffers(HANDLE hFile);

hFile には、GENERIC_WRITE アクセスを持つフラッシュするファイルを指定します
関数が成功すると 0 以外、失敗すると 0 を返します
#include <windows.h>

HANDLE hFile;

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	DWORD dwWriteSize;

	switch (msg) {
	case WM_DESTROY:
		CloseHandle(hFile);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		WriteFile(hFile , TEXT("Kitty on your lap") , 
			17 , &dwWriteSize , NULL);
		FlushFileBuffers(hFile);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
		PSTR lpCmdLine , int nCmdShow) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	hFile = CreateFile(lpCmdLine , GENERIC_WRITE , FILE_SHARE_READ , NULL ,
		CREATE_NEW , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL , TEXT("ファイルを作れません") , NULL , MB_OK);
		return -1;
	}

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}
このプログラムは、コマンドライン引数のファイルを新たに生成し
そのファイルに Kitty on your lap と WriteFile() 関数で書き込みます
この時、即座に FlushFileBuffers() 関数でバッファをフラッシュするため
ウィンドウが生成された時には確実にファイルに文字列が書き込まれていることを保証します


ReadFile()

BOOL ReadFile(
	HANDLE hFile ,
	LPVOID lpBuffer ,
	DWORD nNumberOfBytesToRead ,
	LPDWORD lpNumberOfBytesRead ,
	LPOVERLAPPED lpOverlapped
);
ファイルをバッファに読み込みます

hFile - GENERIC_READ アクセスを持つファイルのハンドルを指定します
lpBuffer - ファイルから読み込んだデータを格納するバッファへのポインタを指定します
nNumberOfBytesToRead - 読み取るバイト数を指定します
lpNumberOfBytesRead - 読み込んだバイト数を格納するDWORD 型へのポインタを指定します
lpOverlapped - OVERLAPPPED 構造体へのポインタを指定します

戻り値 - 成功すると 0 以外、失敗すると 0

WriteFile()

BOOL WriteFile(
	HANDLE hFile ,
	LPCVOID lpBuffer ,
	DWORD nNumberOfBytesToWrite ,
	LPDWORD lpNumberOfBytesWritten ,
	LPOVERLAPPED lpOverlapped
);
ファイルにデータを書き込みます

hFile - GENERIC_WRITE アクセスを持つファイルハンドルを指定します
lpBuffer - 書き込むデータが格納されているバッファへのポインタを指定します
nNumberOfBytesToWrite - 書き込むバイト数を指定します
lpNumberOfBytesWritten - 書き込んだバイト数を格納するDWORD型へのポインタを指定します
lpOverlapped - OVERLAPPED 構造体へのポインタを指定します

戻り値 - 関数が成功すると 0 以外、失敗すると 0

SetFilePointer()

DWORD SetFilePointer(
	HANDLE hFile ,
	LONG lDistanceToMove ,
	PLONG lpDistanceToMoveHigh ,
	DWORD dwMoveMethod
);
ファイルポインタを指定位置に移動します

hFile - GENERIC_READ または GENERIC_WRITE アクセスのファイルハンドルを指定します
lDistanceToMove - ファイルポインタをオフセットで指定します
lpDistanceToMoveHigh - オフセットの上位 32 ビットが格納されているポインタを指定します
dwMoveMethod - ファイルポインタの移動開始地点を定数で指定します

戻り値 - 成功すれば新しいファイルポインタの下位 32 ビット、失敗すると 0xFFFFFFFF

dwMoveMethod には以下の定数を指定します

定数解説
FILE_BEGIN ファイルの先頭を開始点にします
FILE_CURRENT ファイルポインタの現在位置を開始点にします
FILE_END ファイルの終端を開始点にします

SetEndOfFile()

BOOL SetEndOfFile(HANDLE hFile);

現在のファイルポインタに EOF を移動させます

hFile - GENERIC_WRITE アクセスを持つファイルハンドルを指定します

戻り値 - 関数が成功すれば 0 以外、失敗すれば 0 が返ります

FlushFileBuffers()

BOOL FlushFileBuffers(HANDLE hFile);

ファイルのバッファをフラッシュします

hFile - GENERIC_WRITE アクセスを持つフラッシュするファイルを指定します

戻り値 - 関数が成功すれば 0 以外、失敗すれば 0



前のページへ戻る次のページへ