MemoryStreamがマネージドメモリへの読み書きを行うのに対して、UnmanagedMemoryStreamクラスはポインタで表されるアンマネージメモリに対する読み書きを行うためのStreamです。 ポインタからUnmanagedMemoryStreamクラスのインスタンスを作成する以外の操作方法はStreamクラスと同じです。

Streamクラスを使ってポインタに対する読み書きが行えるようになるため、アンマネージメモリに対してBinaryReader・BinaryWriterを使った読み書きができるようになります。

§1 UnmanagedMemoryStreamクラス

UnmanagedMemoryStreamクラスのインスタンスを作成するには、コンストラクタでアンマネージメモリブロックの先頭を表すbyte*型のポインタと、メモリブロックの長さを指定します。

UnmanagedMemoryStreamインスタンスの作成
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] buffer = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

    unsafe {
      // byte配列のポインタを取得する
      fixed (byte* ptr = buffer) {
        // ポインタとメモリブロックの長さを指定してUnmanagedMemoryStreamを作成する
        using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(ptr, buffer.Length)) {
          // 作成したUnmanagedMemoryStreamを使用して1バイトずつ読み込み内容を表示する
          for (int i = 0; i < stream.Length; i++) {
            Console.WriteLine(stream.ReadByte());
          }
        }
      }
    }
  }
}

メモリブロックへの書き込みを行いたい場合は、FileAccess.WriteまたはFileAccess.ReadWriteを指定します。

書き込みを行うUnmanagedMemoryStreamインスタンスの作成
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] buffer = new byte[10];

    unsafe {
      // byte配列のポインタを取得する
      fixed (byte* ptr = buffer) {
        // ポインタへの書き込みを行うUnmanagedMemoryStreamを作成する
        using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(ptr, 0, buffer.Length, FileAccess.Write)) {
          // 作成したUnmanagedMemoryStreamを使用して1バイトずつ書き込む
          for (int i = 0; i < stream.Length; i++) {
            stream.WriteByte(0);
          }
        }
      }
    }
  }
}

IntPtrを使ってUnmanagedMemoryStreamインスタンスを作成することはできないため、VB.NETなどポインタ型を持たない言語では、他の言語で作成されたラッパーを使用するなどするか、Marshalクラスを使用して読み書きを行う必要があります。

UnmanagedMemoryStreamは常に既存のアンマネージメモリブロックを操作します。 MemoryStreamとは異なり、UnmanagedMemoryStream自体はバッファを確保する機能を持ちません。 また、UnmanagedMemoryStreamはコンストラクタで指定したアンマネージメモリブロックの解放も行いません。 メモリブロックの確保・解放などの管理は使用者側で行う必要があります。

UnmanagedMemoryStreamでは、コンストラクタでアクセス可能なメモリブロックの長さを指定することができます。 あらかじめ指定された範囲を超えてアクセスしようとした場合にはNotSupportedExceptionがスローされます。 また、指定されたFileAccessで許可されていないアクセス(読み込み・書き込み)を行おうとした場合にもNotSupportedExceptionがスローされます。

UnmanagedMemoryStreamとスローされる例外の例
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] buffer = new byte[8];

    unsafe {
      fixed (byte* ptr = buffer) {
        // ポインタが表すメモリブロックの先頭から4バイト分までの
        // 書き込みのみを許可するUnmanagedMemoryStreamを作成する
        using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(ptr, 4, 4, FileAccess.Write)) {
          try {
            // FileAccess.Readが指定されていないため、読み込みを行おうとすると
            // NotSupportedExceptionがスローされる
            stream.ReadByte();
          }
          catch (NotSupportedException) {
          }

          // 4バイト分書き込む
          stream.WriteByte(1);
          stream.WriteByte(2);
          stream.WriteByte(3);
          stream.WriteByte(4);

          try {
            // コンストラクタで指定した範囲を超えた書き込みとなるため、
            // NotSupportedExceptionがスローされる
            stream.WriteByte(5);
          }
          catch (NotSupportedException) {
          }
        }
      }
    }

    // 書き込まれた結果を表示する
    Console.WriteLine(BitConverter.ToString(buffer));
  }
}
実行結果
01-02-03-04-00-00-00-00

インスタンスを作成して以降の操作は、通常のStreamと同様に操作を行うことができます。 また、UnmanagedMemoryStreamとBinaryReader・BinaryWriterと組み合わせて使用することもできます。

以下の例は、構造体のポインタからUnmanagedMemoryStreamを作成し、BinaryWriterに使って書き込みを行う例です。

UnmanagedMemoryStreamとBinaryWriterを使って構造体のポインタへの書き込みを行う例
using System;
using System.IO;
using System.Runtime.InteropServices;

class Sample {
  [StructLayout(LayoutKind.Sequential, Pack = 1)]
  struct ARGB {
    public byte A;
    public byte R;
    public byte G;
    public byte B;
  }

  static void Main()
  {
    ARGB color = new ARGB();

    unsafe {
      // 構造体のポインタを取得
      ARGB* ptr = &color;

      // 取得した構造体のポインタへの書き込みを行うUnmanagedMemoryStreamを作成
      using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream((byte*)ptr, 0, sizeof(ARGB), FileAccess.Write)) {
        BinaryWriter writer = new BinaryWriter(stream);

        // 4バイト分書き込む
        writer.Write((uint)0xAABBCCFF);
      }
    }

    // 書き込まれた内容を表示する
    Console.WriteLine("A=0x{0:X2} R=0x{1:X2} G=0x{2:X2} B=0x{3:X2}", color.A, color.R, color.G, color.B);
  }
}
実行結果
A=0xFF R=0xCC G=0xBB B=0xAA

この例で使用しているStructLayout属性についてはフィールドのレイアウト・オフセットを参照してください。



§2 UnmanagedMemoryStreamクラスの使用例

§2.1 バイト配列に変換

以下はUnmanagedMemoryStreamクラスを使って、メモリブロックの内容をバイト配列に変換する例です。 なお、メモリブロックから配列へのコピーにはMarshal.Copyメソッドを用いることもできます。

UnmanagedMemoryStreamクラスを使ってアンマネージメモリブロックの内容をバイト配列に変換する
using System;
using System.IO;
using System.Runtime.InteropServices;

class Sample {
  /// <summary>ポインタの内容をバイト配列に変換するメソッド</summary>
  static unsafe byte[] PtrToByteArray(void* ptr, long length)
  {
    var buffer = new byte[length];

    using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream((byte*)ptr, length, length, FileAccess.Read)) {
      // ポインタより作成したUnmanagedMemoryStreamから全データをバイト配列に読み込む
      stream.Read(buffer, 0, (int)stream.Length);
    }

    return buffer;
  }

  static void Main()
  {
    IntPtr ptr = IntPtr.Zero;

    try {
      ptr = Marshal.AllocHGlobal(0x10);

      unsafe {
        byte[] arr = PtrToByteArray(ptr.ToPointer(), 0x10);

        Console.WriteLine(BitConverter.ToString(arr));
      }
    }
    finally {
      if (ptr != IntPtr.Zero)
        Marshal.FreeHGlobal(ptr);
    }
  }
}

§2.2 memcpy/CopyMemory

以下はUnmanagedMemoryStreamクラスでmemcpy/CopyMemory相当の処理を行う例です。 コピー元・コピー先ブロックのポインタそれぞれからUnmanagedMemoryStreamインスタンスを作成し、CopyToメソッドでコピーを行います。

UnmanagedMemoryStreamクラスでmemcpy/CopyMemory相当の処理を実装する
using System;
using System.IO;
using System.Runtime.InteropServices;

class Sample {
  /// <summary>ポインタの内容を指定されたポインタにコピーするメソッド</summary>
  static unsafe void CopyMemory(void* destination, void* source, long length)
  {
    using (UnmanagedMemoryStream streamSource = new UnmanagedMemoryStream((byte*)source, length, length, FileAccess.Read)) {
      using (UnmanagedMemoryStream streamDestination = new UnmanagedMemoryStream((byte*)destination, 0, length, FileAccess.Write)) {
        streamSource.CopyTo(streamDestination);
      }
    }
  }

  static void Main()
  {
    IntPtr ptrSource = IntPtr.Zero;
    IntPtr ptrDestination = IntPtr.Zero;

    try {
      ptrSource = Marshal.AllocHGlobal(0x10);
      ptrDestination = Marshal.AllocHGlobal(0x10);

      unsafe {
        CopyMemory(ptrDestination.ToPointer(), ptrSource.ToPointer(), 0x10);
      }
    }
    finally {
      if (ptrSource != IntPtr.Zero)
        Marshal.FreeHGlobal(ptrSource);
      if (ptrDestination != IntPtr.Zero)
        Marshal.FreeHGlobal(ptrDestination);
    }
  }
}

このほかにmemcpy/CopyMemory相当の処理を行う手法やそのパフォーマンスについてはバイト列操作 §.メモリブロックのコピー手法とパフォーマンスで解説・検証しています。

この例で用いているCopyToメソッドは.NET Framework 4以降で使用可能なメソッドであるため、.NET Framework 3.5以前の場合はReadメソッド・Writeメソッドを使ってコピー処理を記述する必要があります。 コピー処理の実装例はストリームの基本とStreamクラス §.コピー (CopyTo))で紹介しています。