ファイルマッピング


プロセス間データ共有

ビジネスアプリケーションのレベルになると、単純なツールプログラムとは異なり
自社の他のツールなどと強力に連帯した、高度なサービスを実現しなければなりません
おそらく、そのようなプログラムの最大の壁となるのは「プロセス空間」の壁です

スレッドと異なり、プロセスは全てが独自のメモリ領域を保有しています
プロセスは、他のプロセスのメモリ領域にアクセスしてはなりません
しかし、あなたが作成した複数のプログラムが協調するのは悪いことではありません
むしろ、良質なサービスをユーザーに提供するためには必要なことです

そこで、Windows は単一のファイルを複数のプロセスで共有するために
ファイルマッピングという、記憶領域を共有するオブジェクトを提供しています
概念は簡単で、プロセスの同期で使われた、ミューテックスやセマフォオブジェクトと同じです
ファイルマッピングオブジェクトに記憶領域を関連付け、それに名前をつけるのです
全てのプロセスは、この名前さえわかっていれば、記憶領域のポインタを取得できる仕組みです

ファイルマッピングオブジェクトを生成するには CreateFileMapping() を使います
特定のディスクファイルを共有しても良いですし、システムに任せてもかまいません
HANDLE CreateFileMapping(
	HANDLE hFile ,
	LPSECURITY_ATTRIBUTES lpFileMappingAttributes ,
	DWORD flProtect ,
	DWORD dwMaximumSizeHigh , DWORD dwMaximumSizeLow ,
	LPCTSTR lpName
);
hFile には、共有するファイルのハンドルを指定します
このハンドルは、flProtect で指定する保護フラグと矛盾してはいけません
ここに 0xFFFFFFFF を指定すれば、システムがシステムページングファイルに基き
指定サイズのファイルマッピングオブジェクトを作成します

lpFileMappingAttributes には、セキュリティ属性構造体へのポインタを指定します
NULL を指定すれば、デフォルトのセキュリティで作成され、ハンドルは継承されません

flProtect には、ファイルがマッピングされるビューに対するページ保護フラグです
ビューとはファイルマッピングオブジェクトが管理するファイルのコピーを意味します
プロセスは、直接ファイルにアクセスするのではなく、このビューを介して読み書きします
ここに指定するフラグの詳細は、ページ下部のリファレンスを参照してください
サンプルでは、読み書きを許可する PAGE_READWRITE フラグを指定します

dwMaximumSizeHigh は、ファイルマッピングオブジェクトの最大サイズ上位32ビットです
ここは、ファイルマッピングオブジェクトが 4GB 以上の時に指定します
4GB 以下の場合は、この引数には 0 を指定します

dwMaximumSizeLow は、ファイルマッピングオブジェクトの最大サイズ下位32ビットです
hFile にファイルハンドルを指定している場合で、dwMaximumSizeHigh とこの引数を 0 にすると
hFile で指定したファイルの現在のサイズと等しくなります
hFile に 0xFFFFFFFF を指定した場合は、必ずファイルマッピングオブジェクトサイズを指定します

lpName には、ファイルマッピングオブジェクトの名前を指定します
既存のファイルマッピングオブジェクトの名前と一致した場合は
指定した保護フラグで、ファイルマッピングオブジェクトのハンドルを取得できます

関数が成功すれば、ファイルマッピングオブジェクトのハンドル、失敗すれば NULL が返ります
これで、この名前のファイルマッピングオブジェクトをプロセス間で共有できます

他のプロセスやスレッドから、作成済みのファイルマッピングオブジェクトを得るには
OpenFileMapping() 関数を使います
HANDLE OpenFileMapping(
	DWORD dwDesiredAccess,
	BOOL bInheritHandle,
	LPCTSTR lpName
);
dwDesiredAccess には、ファイルマッピングオブジェクトのアクセスの種類を指定します
読み取り専用か、書き込み可能か等をフラグで指定します
ここには、次の定数を指定することができます

定数解説
FILE_MAP_WRITE 読み書きアクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE で作成されていなければなりません
FILE_MAP_READ 読み取り専用アクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE か PAGE_READ で作成されていなければなりません
FILE_MAP_ALL_ACCESS FILE_MAP_WRITE と同じです
FILE_MAP_COPY 読み書きアクセスです
ただし、元のファイルは変更されず、元のファイルの複製が使われます
対象となるファイルマッピングオブジェクトは
PAGE_WRITECOPY で作成されていなければなりません

bInheritHandle には、新しいプロセスにハンドルを継承させるかを指定します
TRUE を指定すれば、新しいプロセスはハンドルを継承できます

lpName には、開きたい既存のファイルマッピングオブジェクトの名前を指定します
成功すればファイルマッピングオブジェクトのハンドルが、失敗すれば NULL が返ります

プロセスが、共有ファイルを読み書きするにはファイルのビューが必要です
ビューを取得するには MapViewOfFile() 関数を使います
LPVOID MapViewOfFile(
	HANDLE hFileMappingObject ,
	DWORD dwDesiredAccess ,
	DWORD dwFileOffsetHigh , DWORD dwFileOffsetLow,
	DWORD dwNumberOfBytesToMap
);
hFileMappingObject には、ファイルマッピングオブジェクトのハンドルを指定します
dwDesiredAccess には、ビューへのアクセスタイプをフラグで指定します
ここには、OpenFileMapping() 関数の dwDesiredAccess 引数と同じフラグが指定できます

dwFileOffsetHigh は、マッピングを開始するファイルのオフセットの上位32ビットを
dwFileOffsetLow は、下位32ビットを、システムのメモリ割り当て単位の倍数で指定します
割り当て単位を取得するには GetSystemInfo() 関数を使います

dwNumberOfBytesToMap は、マッピングするバイト数を指定します
0 を指定すれば、ファイル全体をマッピングすることを表します
これらの引数を使うことで、指定した位置から指定したサイズでマッピングできます
成功すればマップビューの開始アドレス、そうでなければ NULL が返ります

MapViewOfFile() は、呼び出しプロセスがマップビューのために
提唱メモリアドレスを指定できる MapViewOfFileEx() 拡張関数があります
LPVOID MapViewOfFileEx(
	HANDLE hFileMappingObject,
	DWORD dwDesiredAccess,
	DWORD dwFileOffsetHigh , DWORD dwFileOffsetLow ,
	DWORD dwNumberOfBytesToMap,
	LPVOID lpBaseAddress
);
第 5 引数までは、MapViewOfFile() 関数と同じなので省略します
lpBaseAddress には、マップビューの開始アドレスを指定します
これを用いれば、この関数は lpBaseAddress から始まるマップビューを返します
マップビューを、他のスレッドと共有する時などに便利でしょう

MapViewOfFile() 関数で、プロセスのアドレス空間に取得したマップビューは
UnmapViewOfFile() で解除することができます

BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);

lpBaseAddress には、解除するマップビューのアドレスへのポインタを指定します
この値は MapViewOfFile()、MapViewFoFileEx() が返した値と同じでなければなりません
関数が成功すれば 0 以外、失敗すれば 0 が返ります
成功したならば、ダーティーページが、ディスクに遅延書き込みされます
ダーティーページとは、内容が変更されたページのことです
#include <windows.h>
#define MAP TEXT("KITTY_MAP")

PSTR strAllCmd;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		GetClientRect(hWnd , &rect);

		hdc = BeginPaint(hWnd , &ps);
		DrawText(hdc , strAllCmd , -1 , &rect , DT_LEFT | DT_WORDBREAK);
		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;
	TCHAR strLine[1024];
	HANDLE hMap = CreateFileMapping((HANDLE)0xFFFFFFFF , NULL ,
				PAGE_READWRITE , 0 , 1024 , MAP);

	strAllCmd = MapViewOfFile(hMap , FILE_MAP_WRITE , 0 , 0 , 0);
	if (lstrlen(strAllCmd)) 
		wsprintf(strAllCmd , TEXT("%s\n%s") , strAllCmd , lpCmdLine);
	else lstrcat(strAllCmd , lpCmdLine);

	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	= 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);
	}

	UnmapViewOfFile(strAllCmd);
	CloseHandle(hMap);
	return msg.wParam;
}
このプログラムは、共有ファイルにコマンドライン引数を加算していきます
そして、ウィンドウのクライアント領域に、共有ファイルのビューを表示します

コマンドライン引数に、適当な文字列を指定して
同時にいくつかこのプログラムのプロセスを作成した時に、このプログラムの意味がわかるでしょう
それぞれのプロセスは、お互いにファイルマッピングオブジェクトを共有しています

このプログラムでは、ディスクファイルを使う必要がなかったので使いませんでしたが
ディスクファイルを共有すれば、ファイルマッピングオブジェクトがどのようなタイミングで
共有ファイルに書き込みを行っているかを、リアルタイムで確認することができるでしょう

因みに、アンマップしても、実際にファイルに書き込まれるタイミングは遅延です
指定したタイミングで、確実に共有ファイルにデータを書き込みたいのであれば
ファイルストリームの同様に、フラッシュする必要があります
データをフラッシュするには FlushViewOfFile() 関数を使います

BOOL FlushViewOfFile(LPCVOID lpBaseAddress , DWORD dwNumberOfBytesToFlush);

lpBaseAddress には、ビューの先頭アドレスへのポインタを指定します
dwNumberOfBytesToFlush には、書き込むバイト数を指定します
0 を指定すれば、先頭からマッピングの終端までが書き込まれます
関数が成功すると 0 以外、失敗すると 0 が返ります


ファイルマッピングオブジェクトを使えば、異なるプロセスの間でデータ交換ができます
交換するタイミングとデータ型が確定している状態で
他社製品との通信の必要がない場合は、クリップボードよりも便利でしょう

ファイルマッピングオブジェクトが威力を発揮するもう一つのケースに
DLL で全てのプロセスが共有する変数を作る時があげられます
DLL は、異なるプロセスから関数などを呼び出すことができます
しかし、プロセス A とプロセス B が同時に同じ DLL C をメモリに読み込んだとしても
プロセスは独自のメモリ空間に DLL を配置するだけで、変数を共有しません

DLL CDLL C
||
プロセス Aプロセス B

これは、当然のことで、そうしなければ競合が起きてプログラムが滅茶苦茶になります
もし全てのプロセスが DLL の静的変数をデフォルトで共有してしまったら
プログラマはほとんどのケースで、自動変数以外を DLL に書くことができなくなってしまいます

ですが、特殊なケースにおいて DLL がプロセス全体に共有する情報を持つ必要もあるでしょう
DLL は、今、自分がどのくらいのプロセスに利用されているのかを知りたがるかもしれません
だとすれば、DLL は全てのプロセスで共有できる変数をほしがるでしょう
これを実現するには、ファイルマッピングオブジェクトが最も適しています

共通のファイルマッピングオブジェクト
||
DLL CDLL C
||
プロセス Aプロセス B

こうすれば、DLL は全てのプロセスに共有される変数を持つことができますし
ファイルマッピングオブジェクトの処理をブラックボックス化させ
プロセス間のデータ通信をよりハイレベルにサポートする関数を提供することもできます
/*test.h*/
#ifdef __cplucplus
#define DLL_EXPORT extern "C" __declspec (dllexport)
#else 
#define DLL_EXPORT __declspec (dllexport)
#endif

DLL_EXPORT DWORD CALLBACK GetDllUsers();
/*dll_test.c*/
#include <windows.h>
#include "test.h"

#define MAP TEXT("KITTY_MAP")
DWORD * pdwUsers;

int WINAPI DllEntryPoint(HINSTANCE hInstance , DWORD fdwReason , PVOID pvReserved) {
	static HANDLE hMap;

	switch(fdwReason) {
	case DLL_PROCESS_ATTACH:
		hMap = CreateFileMapping((HANDLE)0xFFFFFFFF , NULL ,
				PAGE_READWRITE , 0 , sizeof (DWORD) , MAP);
		pdwUsers = (DWORD *)MapViewOfFile(
				hMap , FILE_MAP_WRITE , 0 , 0 , 0);
		*pdwUsers += 1;
		break;
	case DLL_PROCESS_DETACH:
		*pdwUsers += -1;
		UnmapViewOfFile(pdwUsers);
		CloseHandle(hMap);
	}
	return TRUE;
}

DLL_EXPORT DWORD CALLBACK GetDllUsers() {
	return *pdwUsers;
}
#include <windows.h>
#include "test.h"

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	TCHAR str[128];

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		wsprintf(str ,
			TEXT("DLL を使用しているプロセス = %d") , GetDllUsers());
		TextOut(hdc , 0 , 0 , str , lstrlen(str));
		EndPaint(hWnd , &ps);
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

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

	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	= 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;
}
このプログラムは、ウィンドウのクライアント領域に
DLL_TEST.DLL を利用しているプロセスの数を表示します

DLL_TEST.DLL は、自分自身を利用しているプロセスの数を返す
GetDllUsers() 関数をエクスポートしているので、プログラムはこれを利用しています

実は、一般的な Win32 アプリケーション開発環境には
#pragma プリプロセッサディレクティブを用いることで
静的に、共有メモリ空間を宣言する方法がサポートされています

Microsoft Visual C++ であれば、プリプロセッサに共有メモリ空間を伝え
さらに #pragma からリンカを操作して、コンパイル時に簡単に共有変数を指定できます
しかし、この方法はあまりにも開発環境に依存するため、紹介しません
ソースの移植性を考えて、筆者は Win32 API で動的にデータを共有する方法を取っています


CreateFileMapping()

HANDLE CreateFileMapping(
	HANDLE hFile ,
	LPSECURITY_ATTRIBUTES lpFileMappingAttributes ,
	DWORD flProtect ,
	DWORD dwMaximumSizeHigh , DWORD dwMaximumSizeLow ,
	LPCTSTR lpName
);
ファイルマッピングオブジェクトを作成します

hFile - 共有するファイルのハンドルを指定します
lpFileMappingAttributes - セキュリティ属性構造体へのポインタを指定します
flProtect - ビューに対するページ保護フラグを指定します
dwMaximumSizeHigh - ファイルの最大サイズ上位32ビットを指定します
dwMaximumSizeLow - ファイルの最大サイズ下位32ビットを指定します
lpName - ファイルマッピングオブジェクトの名前を指定します

戻り値 - ファイルマッピングオブジェクトのハンドル、失敗すれば NULL

flProtect には、次の定数のいずれか指定できます

定数解説
PAGE_READONLYコミット済み領域への
読み取り専用アクセスを許可します
hFile パラメータで指定したハンドルは
GENERIC_READ 属性である必要があります
PAGE_READWRITEコミット済み領域への
読み取り/書き込みアクセスを許可します
hFile パラメータで指定したハンドルは
GENERIC_READ と GENERIC_WRITE 属性である必要があります
PAGE_WRITECOPYコミット済み領域への
書き込み時コピーアクセスを許可します
hFile パラメータで指定したハンドルは
GENERIC_READ と GENERIC_WRITE 属性である必要があります

flProtect には、以下のセクション属性を上記の定数と組み合わせて指定できます

定数解説
SEC_COMMIT デフォルトです
あるセクションの全てのページに対して
メモリ内、またはディスク上のページングファイル内に
物理記憶を割り当てます
SEC_IMAGE hFile で指定したファイルが実行可能イメージファイルです
マッピング情報と、ファイル保護はこのイメージファイルから取られるため
この値を指定した場合、他の属性は無視されます
SEC_NOCACHE あるセクションの全てのページをキャッシュ不能とします
この値を指定する場合、SEC_RESERVE
または SEC_COMMIT も設定する必要があります
SEC_RESERVE 物理記憶を割り当てず、あるセクションの全てのページを予約します
予約ページは VirtualAlloc() 関数でコミットすることができます
この属性は hFile パラメータが 0xFFFFFFFF でなければなりません

OpenFileMapping()

HANDLE OpenFileMapping(
	DWORD dwDesiredAccess,
	BOOL bInheritHandle,
	LPCTSTR lpName
);
作成済みのファイルマッピングオブジェクトを開きます

dwDesiredAccess - 、ファイルマッピングオブジェクトのアクセスの種類を指定します
bInheritHandle - TRUE を指定すれば、新しいプロセスはハンドルを継承できます
lpName - 開きたい既存のファイルマッピングオブジェクトの名前を指定します

戻り値 - ファイルマッピングオブジェクトのハンドル、失敗すれば NULL

dwDesiredAccess には以下の定数のいずれかを指定できます

定数解説
FILE_MAP_WRITE 読み書きアクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE で作成されていなければなりません
FILE_MAP_READ 読み取り専用アクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE か PAGE_READ で作成されていなければなりません
FILE_MAP_ALL_ACCESS FILE_MAP_WRITE と同じです
FILE_MAP_COPY 読み書きアクセスです
ただし、元のファイルは変更されず、元のファイルの複製が使われます
対象となるファイルマッピングオブジェクトは
PAGE_WRITECOPY で作成されていなければなりません

MapViewOfFile()

LPVOID MapViewOfFile(
	HANDLE hFileMappingObject ,
	DWORD dwDesiredAccess ,
	DWORD dwFileOffsetHigh , DWORD dwFileOffsetLow,
	DWORD dwNumberOfBytesToMap
);
ファイルマッピングオブジェクトのマップビューを取得します

hFileMappingObject - ファイルマッピングオブジェクトのハンドルを指定します
dwDesiredAccess - ビューへのアクセスタイプをフラグで指定します
dwFileOffsetHigh - ファイルのオフセットの上位32ビットを指定します
dwFileOffsetLow - フィあるのオフセットの下位32ビットを指定します
dwNumberOfBytesToMap - マッピングするバイト数を指定します

戻り値 - マップビューの開始アドレス、そうでなければ NULL

dwDesiredAccess には以下の定数のいずれかを指定できます

定数解説
FILE_MAP_WRITE 読み書きアクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE で作成されていなければなりません
FILE_MAP_READ 読み取り専用アクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE か PAGE_READ で作成されていなければなりません
FILE_MAP_ALL_ACCESS FILE_MAP_WRITE と同じです
FILE_MAP_COPY 読み書きアクセスです
ただし、元のファイルは変更されず、元のファイルの複製が使われます
対象となるファイルマッピングオブジェクトは
PAGE_WRITECOPY で作成されていなければなりません

MapViewOfFileEx()

LPVOID MapViewOfFileEx(
	HANDLE hFileMappingObject ,
	DWORD dwDesiredAccess ,
	DWORD dwFileOffsetHigh , DWORD dwFileOffsetLow,
	DWORD dwNumberOfBytesToMap ,
	LPVOID lpBaseAddress
);
ファイルマッピングオブジェクトのマップビューを取得します

hFileMappingObject - ファイルマッピングオブジェクトのハンドルを指定します
dwDesiredAccess - ビューへのアクセスタイプをフラグで指定します
dwFileOffsetHigh - ファイルのオフセットの上位32ビットを指定します
dwFileOffsetLow - フィあるのオフセットの下位32ビットを指定します
dwNumberOfBytesToMap - マッピングするバイト数を指定します
lpBaseAddress - マップビューの開始アドレスを指定します

戻り値 - マップビューの開始アドレス、そうでなければ NULL

dwDesiredAccess には以下の定数のいずれかを指定できます

定数解説
FILE_MAP_WRITE 読み書きアクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE で作成されていなければなりません
FILE_MAP_READ 読み取り専用アクセスです
対象となるファイルマッピングオブジェクトは
PAGE_READWRITE か PAGE_READ で作成されていなければなりません
FILE_MAP_ALL_ACCESS FILE_MAP_WRITE と同じです
FILE_MAP_COPY 読み書きアクセスです
ただし、元のファイルは変更されず、元のファイルの複製が使われます
対象となるファイルマッピングオブジェクトは
PAGE_WRITECOPY で作成されていなければなりません

UnmapViewOfFile()

BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);

ファイルのビューを、呼び出し側プロセスのアドレス空間からアンマップします

lpBaseAddress - 解除するマップビューのアドレスへのポインタを指定します

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

FlushViewOfFile()

BOOL FlushViewOfFile(LPCVOID lpBaseAddress , DWORD dwNumberOfBytesToFlush);

ファイルビューのデータをディスクに書き込みます

lpBaseAddress - ビューの先頭アドレスへのポインタを指定します
dwNumberOfBytesToFlush - 書き込むバイト数を指定します

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



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