メモリマップドファイル

仕事で使う用があったのでババっと勉強してみました。簡単に言うとファイルをメモリとしてマッピングすることによりファイルをあたかもメモリのように扱う技術です。何が嬉しいかというと、ファイルなのでどのアプリケーションからでもアクセス可能なのでアプリケーション間でのメモリ共有ができるという点でしょか。本来の使い方はメモリ共有っぽいのですが今回仕事で必要だった機能としては、メモリが全然足りないので擬似的に大量のメモリを扱うため仮想メモリを構築するという目的でした。ファイルをメモリとして扱うのでメモリ容量がHDD容量に匹敵することになるので大量メモリ消費する数値計算などで大活躍できそうです。尤も、ファイルアクセスなので速度の大幅減少は避けられないですが。

やり方としては、まずは必要な大きさのファイルを作成、んでそのファイルを開いてマッピングして先頭ポインタを取ってくる。あとはそのポインタをゴリゴリ使うわけである。

HANDLE hFile = ::CreateFile( m_szFileName, GENERIC_READ | GENERIC_WRITE ,0, 
	NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL ,NULL );
HANDLE hMap = ::CreateFileMapping( hFile, NULL, PAGE_READWRITE, 0, 0, NULL );
void* pPointer = MapViewOfFile( hMap, FILE_MAP_WRITE, 0, 0, dwSize );

んで使い終わったら閉じるわけである。

::UnmapViewOfFile( pPointer );
::CloseHandle( hMap );
::CloseHandle( hFile );

基本的にはこれだけである。しかしファイルをどう扱うかとか考えると意外に面倒だったりします。そのあたりをちょい考慮にいれてラッピングしてみました。ファイルを一時ファイル置き場に一時ファイルとして作成して使い終わったらファイルを削除してくれます。

以下、長くなりそうなので

#include <new>
#include <windows.h>

class MemoryMapedFile
{
public:
	MemoryMapedFile():m_hFile( INVALID_HANDLE_VALUE ),m_hMap( NULL ),m_pPointer( NULL ),m_dwSize( 0 ){}
	MemoryMapedFile( DWORD dwSize ){
		Allocate( dwSize );
	}
	
	virtual ~MemoryMapedFile(){
		Release();
	}
	
	//仮想メモリ確保
	bool Allocate( DWORD dwSize );

	//明示的解放
	void Release();
	
	//生ポインタGet
	LPVOID GetPtr(){
		return m_pPointer;
	}

	//無理矢理byte変換
	BYTE& operator[]( unsigned int n );
	
private:
	//コピー禁止
	MemoryMapedFile( const MemoryMapedFile& );
	MemoryMapedFile& operator=( const MemoryMapedFile& );

	HANDLE m_hFile;
	HANDLE m_hMap;
	LPVOID m_pPointer;
	DWORD m_dwSize;

	TCHAR m_szFileName[MAX_PATH];
};

//以下、CPP側
bool MemoryMapedFile::Allocate( DWORD dwSize )
{
	//初期化
	Release();
	
	//一時ファイル作成
	TCHAR szTmpPath[MAX_PATH];
	if( GetTempPath( MAX_PATH, szTmpPath ) == 0 ){
		//なぜか失敗
		return false;
	}
	if( GetTempFileName( szTmpPath, "MAP", 0, m_szFileName ) == 0 ){
		//やっぱりなぜか失敗
		return false;
	}
	//ファイル生成に成功。
	m_hFile = ::CreateFile( m_szFileName, GENERIC_READ | GENERIC_WRITE ,0, 
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL ,NULL );
	if( m_hFile == INVALID_HANDLE_VALUE ){
		//どうしてか失敗
		::DeleteFile( m_szFileName );
		return false;
	}
	
	//マッピング
	m_hMap = ::CreateFileMapping( m_hFile, NULL, PAGE_READWRITE, 0, dwSize, NULL );
	if( m_hMap == NULL ){
		//よくわからないけど失敗
		::CloseHandle( m_hFile );
		m_hFile = INVALID_HANDLE_VALUE;
		::DeleteFile( m_szFileName );
		
		return false;
	}
	m_pPointer = MapViewOfFile( m_hMap, FILE_MAP_WRITE, 0, 0, dwSize );
	if( m_pPointer == NULL ){
		//何でか失敗
		::CloseHandle( m_hMap );
		m_hMap = NULL;
		::CloseHandle( m_hFile );
		m_hFile = INVALID_HANDLE_VALUE;
		::DeleteFile( m_szFileName );
		return false;
	}
	m_dwSize = dwSize;
	
	//成功
	return true;
}


BYTE& MemoryMapedFile::operator[]( unsigned int n )
{
#ifdef _DEBUG
	if( n < m_dwSize ){
		return reinterpret_cast< BYTE* >( m_pPointer )[n];
	}else{
		throw "Buffer Over Flow";
	}
#else
	return reinterpret_cast< BYTE* >( m_pPointer )[n];
#endif
}


void MemoryMapedFile::Release()
{
	if( m_pPointer != NULL ){
		::UnmapViewOfFile( m_pPointer );
		m_pPointer = NULL;
		m_dwSize = 0;
	}
	if( m_hMap != NULL ){
		::CloseHandle( m_hMap );
		m_hMap = NULL;
	}
	if( m_hFile != INVALID_HANDLE_VALUE ){
		::CloseHandle( m_hFile );
		m_hFile = INVALID_HANDLE_VALUE;
		::DeleteFile( m_szFileName );
	}
}

一応、商品として通用させたいという気持ちもあってゴテゴテにエラーチェックを入れておきました(^^; とりあえずリソースリークとかそういうのは発生しないはずです。
で、このクラスなんですがちょい欠点があります。オブジェクト生成・解体ごとに一時ファイルを生成、削除しているので結構重いです。しょっちゅうマッピングする場合なら一時ファイルを使いまわせば大幅な速度向上が期待できますがこの実装が結構面倒。外注に頼んだらそのあたりを考慮したコードが帰ってきたんですが別の問題が発生しており、プログラム終了後も一時ファイルが残るという。これはやばいだろうと思うんですけど。一時ファイルとはいえ自動削除はやってくれないはずなのでプログラムを走らせる度に巨大な一時ファイルが生成されて気が付いたら一時ファイルでHDDがいっぱいとかなりかねないので。
で、解決方法は根本的に構造を変えたほうがよいのかな?と。具体的にはマップドファイルオブジェクトを作る工場をシングル㌧で生成して、工場内で一時ファイルの管理を行なって適度に使いまわして、工場解体と同時に全てのファイルを削除。多分これが一番かなと考えているのですが実装めどそうなので割愛(ぉ