マップドファイルからの読み取り
事前にマップされたメモリ位置を使用するには、MemoryMappedViewAccessorクラスのメソッドを使う必要があります。例えば、ファイルマップの先頭から10バイトを読み取るには、ReadByteメソッドを次のように使います。
...
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
byte[] buffer = new byte[10];
for (int index = 0; index < buffer.Length; index++)
{
buffer[index] = accessor.ReadByte(index);
}
Readメソッドは、特定の汎用オブジェクトの内容を埋めることも、Read<T>またはReadArray<T>でジェネリックを使って、より具体的なオブジェクトを受け取ることもできます。例えば、GUID型の(構造体として定義された)オブジェクトがある場合、次の2つのReadNNNメソッド呼び出しの結果は同じようになります。
// method 1: byte[] buffer = new byte[16]; accessor.ReadArray(0, buffer, 0, buffer.Length); Guid guid = new Guid(buffer); MessageBox.Show(guid.ToString()); // method 2: Guid guid2 = new Guid(); accessor.Read(0, out guid2); MessageBox.Show(guid2.ToString());
どちらのReadメソッド呼び出しでも、読み取り開始位置を指定する必要があることに注意してください。このゼロから始まるオフセットは、必ずしも元のファイルに対する位置ではなく、常にマップされたビューに対する位置になります。メモリマッピングオブジェクトを作成するときに、ファイルを操作するためのメモリのウィンドウを指定する必要があります(図1)。前に示したコードのようにオフセットを指定しなければ、ビューはファイルの先頭から始まるものと見なされます。
柔軟性を持たせるために、ゼロのオフセットから開始して、ファイルの長さ全体をマップすることも、中間から開始して、ファイルの一部だけをマップすることもできるようになっています。アクセサオブジェクトによる読み取りは、ビューに対するオフセットを使って行われます。つまり、元のファイルのオフセットは、ビューの開始位置にビューのオフセットを加えたものになります。
また、メモリマッピングオブジェクトと、その元になるファイルには、オペレーティングシステムハンドルがあることにも注意してください。ですから、使い終わったオブジェクトは忘れずに廃棄することが大切です。そうしないと、ガベージコレクションが作動するまで、オブジェクトはいつまでもオープンされたままになります。try-finallyブロックまたはC#のusing文を使うことをお勧めします。
.NETストリームオブジェクトで十分に用は足りているものの、メモリマップドファイルの利点を活用したいという人にうってつけの機能があります。MemoryMappedFileクラスには、MemoryMappedViewStreamオブジェクトを返すCreateViewStreamという便利なメソッドがあります。このオブジェクトを使うと、マップされたビューへのシーケンシャルアクセスを行うことができます。ランダムアクセスが可能なアクセサオブジェクトを使う場合に比べて、このようにシーケンシャルアクセスしかできないことが、マップされたビューのストリームを使う最大の弱点です。この制限に不満がなければ、CreateViewStreamメソッドは良い味方になります。
オブジェクト間およびプロセス間でのメモリの共有
前のセクションでは、メモリマップドファイルを使うと、単純なメモリ操作でファイルの内容に簡単にアクセスできることを示しました。ここではこの知識を基に、アプリケーション内およびプロセス間でメモリを共有する方法について説明します。
ファイルの内容をメモリにマッピングするときは、何よりまず、ディスク上のどのファイルのどの部分をマッピングするかを指定する必要があります。ここで留意すべきことは、同じファイルを何度でもマップできるという点です。たとえマップされる領域が同じでも、重なり合っていてもかまいません(図2)。

この機能を利用することによって、同時実行やロックを気にせずに、複数のスレッドからファイルの内容にアクセスできるようになります。メモリブロックを読み書きする方法さえ知っていれば十分で、それには前に説明したビューアクセサクラスを使います。次の例は、ファイルの先頭を複数のアクセサオブジェクトにマップする方法を示しています。当然、ファイル名は新しいビューアクセサを作成するたびに一致していなければなりません。次のように同じメモリマッピングオブジェクトを2回使えば、これは簡単に実現できます。
...
MemoryMappedViewAccessor accessor1 =
mmf.CreateViewAccessor();
MemoryMappedViewAccessor accessor2 =
mmf.CreateViewAccessor();
// write
byte writeChr = Encoding.ASCII.GetBytes("Z")[0];
accessor1.Write(0, writeChr);
// read
byte readChr = accessor2.ReadByte(0);
string status = (readChr == writeChr) ? "Match!" : "No match!";
MessageBox.Show(status); // match
ビューのメモリブロックへの書き込みが完了した時点で、ファイルの内容が変更されていることに注意してください。変更されたデータをオペレーティングシステムが直ちにディスクにフラッシュしないこともありますが、通常はほぼ即座に行われます。コミット操作やフラッシュ操作を別途行う必要はありません。これがメモリマップドファイルを使う利点の1つです。
マップされたファイルをプロセス間で共有するには、ビューに名前を付ける必要があります。名前を使うことによって、同期されたビューを複数のプロセスで開けるようになります。言うまでもなく、システムの他のオブジェクト名と重複する名前は使えません。例えば、プロセス間で文字列を送信したいとします。メモリにマップされた名前付きのビューを開き、簡単な文字列をビューに書き込むコードは次のようになります。
MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(
"my-mmf-map-name", 1000);
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
string message = "Hello, Memory-Mapped World!";
byte[] asciiBytes = Encoding.ASCII.GetBytes(message);
accessor.WriteArray(0, asciiBytes, 0, asciiBytes.Length);
MessageBox.Show("Message written.");
このコードでは、データを入れる物理ファイルが使われていないことに注意してください。そのため、CreateOrOpenメソッドを呼び出すときに容量パラメータを指定する必要があります。上のコードでは、容量を1,000バイトに設定しています。この容量はメモリブロックのサイズを定義します。これについては後で詳しく述べます。プロセス間の情報共有の例に戻ると、次にすべきことは、別のプロセスで類似の名前のビューを使って文字列を読み出すことです。
MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(
"my-mmf-map-name", 1000);
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
byte byteValue;
int index = 0;
StringBuilder message = new StringBuilder();
do
{
byteValue = accessor.ReadByte(index);
if (byteValue != 0)
{
char asciiChar = (char)byteValue;
message.Append(asciiChar);
}
index++;
} while (byteValue != 0);
MessageBox.Show("Found text: \""+message+"\".");
このコードでは、MemoryMappedFileクラスのCreateOrOpen静的メソッドを使い、メモリにマップされた同じビューを別のプロセスで開いています。次に、前と同じようにアクセサオブジェクトを作成し、ゼロ終端バイトが出現するまでデータをバイト単位で読み取っています。その後で、メッセージを処理しています。この例では画面上にメッセージが表示されます。このように、プロセス間通信(IPC)をとても簡単な方法で処理できます。

