System.IO.MemoryMappedFiles.MemoryMappedFileクラスは、.NET 4.0から
追加されたクラスです。文字通り、メモリ上にマッピングされたファイルを扱います。
MMFと略したりします。
メモリマップトファイルは、昔からWin32 APIとして用意されていましたが
4.0より.NET Frameworkにクラスとして登場しました。
MemoryMappedFileの特徴は、ランダムアクセスが速い事です。
メモリ内にマップされているのでシークする必要がありません。
逆にシーケンシャルアクセスは苦手です。
逆にFileStreamはシーケンシャルアクセスが得意で、ランダムアクセスが苦手です。
また、MemoryMappedFileは共有メモリとしても利用出来ます。
つまり複数のプロセス間で共有することが出来ます。
で、実際の利用方法ですが
MemoryMappedFileオブジェクトは、newするのではなくMemoryMappedFileクラス
に用意されている以下のstaticメソッドを利用して構築します。
- CreateFromFile
- ディスク上のファイルからMemoryMappedFileを作成します。
- CreateNew
- ディスク上のファイルと関連づけられていないMemoryMappedFileを作成します。
- CreateOrOpen
- ディスク上のファイルと関連づけられていないMemoryMappedFileを作成もしくは開きます。
- OpenExisting
- 指定された名前の既にマッピングされているMemoryMappedFileを開きます。
上記のどのメソッドを利用する場合でも、通常MemoryMappedFileオブジェクトを
構築する際は、以下のように2つのusingを利用します。
using (var mmf = MemoryMappedFile.CreateFromFile(BIN_FILE_NAME)) { using (var accessor = mmf.CreateViewAccessor()) { // データの読み書きを行う。 } }
一つ目のusingでMemoryMappedFileオブジェクトを取得し、二つ目のusingで
データにアクセスするためのMemoryMappedViewAccessorを取得します。
後は取得したMemoryMappedViewAccessorでデータを読み書きします。
データの読み書きには以下のメソッドを利用します。
- ReadXXX (XXXの部分にはそれぞれの型名が入る。(int32, byte, double, charなど))
- 指定された型でデータを取得します。
- Read
- T型の構造体を指定してデータを読み取ります。
- ReadArray
- T型の配列を指定してデータを読み取ります。
- Write
- 指定したデータを書き込み。(各型用にオーバーロードが用意されています。)
- Write
- T型の構造体を指定してデータを書き込みます。
- WriteArray
- T型の配列を指定してデータを書き込みます。
以下、データの基本的な読み書きのサンプルです。
以下のサンプルでは最初に大きなバイナリファイルを作成して
そのファイルをメモリ上にマッピングし、データを読み書きしています。
using System; using System.IO; using System.IO.MemoryMappedFiles; namespace MemoryMappedFilesSample { class Program { private const string BIN_FILE_NAME = "bigdata.bin"; static void Main(string[] args) { if (File.Exists(BIN_FILE_NAME)) { File.Delete(BIN_FILE_NAME); } // // 元となるバイナリファイルを作成. // File.WriteAllBytes(BIN_FILE_NAME, new byte[1000000]); // // 元々ファイルが存在する状態での読み込み. // using (var mmf = MemoryMappedFile.CreateFromFile(BIN_FILE_NAME)) { using (var accessor = mmf.CreateViewAccessor()) { // // 書き換える前のデータを確認. // Console.WriteLine(accessor.ReadByte(500000)); // // データを書き換える. // accessor.Write(500000, (byte) 77); // // 書き換えた位置のデータを取得して確認. // Console.WriteLine(accessor.ReadByte(500000)); // // 配列を指定して指定範囲のデータを取得. // byte[] buf = new byte[10]; accessor.ReadArray<byte>(499996, buf, 0, buf.Length); Console.WriteLine(string.Join<byte>(",", buf)); // // 配列を指定して指定範囲のデータを設定. // buf = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; accessor.WriteArray<byte>(499996, buf, 0, buf.Length); buf = new byte[10]; accessor.ReadArray<byte>(499996, buf, 0, buf.Length); Console.WriteLine(string.Join<byte>(",", buf)); } } } } }
実行結果は以下のようになります。
0 77 0,0,0,0,77,0,0,0,0,0 10,20,30,40,50,60,70,80,90,100
次に、MemoryMappedFileを共有メモリとして利用しているサンプルです。
以下のサンプルは2つのexeを用意して、一つはデータの書き込み、一つはデータの読み込みを
同じ共有メモリに対して行います。その際、MemoryMappedFileには同じ名前を指定するようにします。
まず書き込み側。
using System; using System.IO.MemoryMappedFiles; using System.Threading; namespace CreateNewMemoryMappedFile { class Program { static void Main(string[] args) { using (var mutex = new Mutex(true, "MMF_MUTEX")) { Console.WriteLine("===LOCK=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond); // // 新規でMemory Mapped Fileを作成. // using (var mmf = MemoryMappedFile.CreateNew("data.bin", 500)) { using (var accessor = mmf.CreateViewAccessor()) { accessor.Write(10, 12345); mutex.ReleaseMutex(); Console.WriteLine("12345 -- write"); Console.WriteLine("===RELEASE=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond); Console.WriteLine("CreateNewMemoryMappedFile -- Write"); Console.ReadLine(); } } } } } }
次に読み込み側
using System; using System.IO.MemoryMappedFiles; using System.Threading; namespace ReadSharedMemoryMappedFile { class Program { static void Main(string[] args) { Mutex mutex = null; while (mutex == null) { try { mutex = Mutex.OpenExisting("MMF_MUTEX"); } catch { // re-try. Console.WriteLine("Retry...."); } } // // すでに存在するMemory Mapped Fileを読み込み. // using (mutex) { if (mutex.WaitOne()) { Console.WriteLine("===LOCK=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond); int data = -1; using (var mmf = MemoryMappedFile.OpenExisting("data.bin")) { using (var accessor = mmf.CreateViewAccessor()) { data = accessor.ReadInt32(10); } } mutex.ReleaseMutex(); Console.WriteLine("{0} -- Read", data); Console.WriteLine("===RELEASE=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond); Console.WriteLine("ReadSharedMemoryMappedFile -- Read"); Console.ReadLine(); } } } } }
最後に二つを起動するプログラム
using System; using System.Diagnostics; namespace MemoryMappedFilesSample2 { class Program { static void Main(string[] args) { // // 2つのExeを順に起動. // Process.Start("CreateNewMemoryMappedFile.exe"); Process.Start("ReadSharedMemoryMappedFile.exe"); Console.WriteLine("MemoryMappedFileSample2 -- Main"); Console.ReadLine(); } } }
実行すると、書き込み側では
12345 -- Write
と表示され、読み込み側では
12345 -- Read
と表示されます。
また、説明の部分でも記述しましたがaccessorにデータを設定する際に
構造体を指定することも出来ます。(Read
こうするとデータを纏めて読み書きできます。
構造体を利用する際の注意点ですが、この場合の構造体には
参照を含む型は定義できません。つまり構造体のメンバーとして
public char[] Chars;
とは定義できません。定義できるのは参照を含まない型のみです。(intなど)
んじゃ、文字列はどうするの?となるのですが
文字列の場合は、バイト配列に変換してWriteArray
当然読み込みはReadArray
以下、構造体と文字列を使用したサンプルです。
using System; using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; using System.Text; namespace MemoryMappedFilesSample3 { class Program { static void Main(string[] args) { // // 500バイトのMMFを新規作成する. // using (var mmf = MemoryMappedFile.CreateNew("data.bin", 500)) { using (var accessor = mmf.CreateViewAccessor()) { // // 構造体と文字列を書き込み. // 構造体を指定する場合、その構造体に参照を含む型が存在していると // エラーとなる。 // // 文字列を書き込む場合は、Encodingクラスを利用してバイト配列に変換し // WriteArray<T>メソッドを利用して書き込む。 // // 読み込む場合は、ReadArray<T>を利用して読み込む. // // 構造体のサイズを取得する場合は、Marshal.SizeOfメソッドを利用する. // Data data; data.X = 100; data.Y = 200; byte[] bytes = Encoding.UTF8.GetBytes("Hello World"); // // データ書き込み. // const int START_POSITION = 10; int structSize = Marshal.SizeOf(data); accessor.Write<Data>(START_POSITION, ref data); accessor.WriteArray<byte>(START_POSITION + structSize, bytes, 0, bytes.Length); // // データ読み込み. // Data data2; accessor.Read<Data>(START_POSITION, out data2); byte[] bytes2 = new byte[bytes.Length]; structSize = Marshal.SizeOf(data2); accessor.ReadArray<byte>(START_POSITION + structSize, bytes2, 0, bytes2.Length); // // 確認. // Console.WriteLine(data2); Console.WriteLine(Encoding.UTF8.GetString(bytes2)); } } } } struct Data { public int X; public int Y; public override string ToString() { return string.Format("X={0}, Y={1}", X, Y); } } }
実行すると
X=100, Y=200 Hello World
と表示されます。
最後に、unsafeブロックを使用して直接ポインタ経由で
データを読み書きするサンプルです。
ポインタを取得するには、まずメモリマップトファイルのビューハンドルを
取得し、そこからメモリブロックのポインタを取得します。
ビューハンドルはMemoryMappedViewAccessorの以下のメソッドで取得します。
accessor.SafeMemoryMappedViewHandle
ビューハンドルを取得したらそのままAcquirePointerメソッドでポインタを取ります。
後はそのポインタを利用して読み書きします。
以下、サンプルです。
using System; using System.IO.MemoryMappedFiles; namespace MemoryMappedFilesSample4 { class Program { static void Main(string[] args) { unsafe { using (var mmf = MemoryMappedFile.CreateOrOpen("data.bin", 500)) { // // ポインタを利用してデータ書き込み. // using (var accessor = mmf.CreateViewAccessor()) { // // メモリマップトファイルのビューハンドルを取得し // メモリブロックのポインタを取得. // byte* p = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref p); // // 取得したポインタを任意の型のポインタにキャストしてデータを設定. // Data* data = (Data*) p; data->X = 100; data->Y = 200; } // // ポインタを取得してデータを読み込み. // using (var accessor = mmf.CreateViewAccessor()) { // // メモリマップトファイルのビューハンドルを取得し // メモリブロックのポインタを取得. // byte* p = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref p); // // 書き込み時に利用した型のポインタにキャストしてデータを読み込み. // Data* data = (Data*) p; Console.WriteLine(*data); } } } } } struct Data { public int X; public int Y; public override string ToString() { return string.Format("X={0}, Y={1}", X, Y); } } }
================================
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場