MemoryStreamがマネージドメモリへの読み書きを行うのに対して、UnmanagedMemoryStreamクラスはポインタで表されるアンマネージメモリに対する読み書きを行うためのStreamです。 ポインタからUnmanagedMemoryStreamクラスのインスタンスを作成する以外の操作方法はStreamクラスと同じです。
Streamクラスを使ってポインタに対する読み書きが行えるようになるため、アンマネージメモリに対してBinaryReader・BinaryWriterを使った読み書きができるようになります。
UnmanagedMemoryStreamクラス
UnmanagedMemoryStreamクラスのインスタンスを作成するには、コンストラクタでアンマネージメモリブロックの先頭を表すbyte*
型のポインタと、メモリブロックの長さを指定します。
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
を指定します。
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がスローされます。
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に使って書き込みを行う例です。
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属性についてはフィールドのレイアウト・オフセットを参照してください。
UnmanagedMemoryStreamクラスの使用例
バイト配列に変換
以下はUnmanagedMemoryStreamクラスを使って、メモリブロックの内容をバイト配列に変換する例です。 なお、メモリブロックから配列へのコピーにはMarshal.Copyメソッドを用いることもできます。
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);
}
}
}
memcpy/CopyMemory
以下はUnmanagedMemoryStreamクラスでmemcpy
/CopyMemory
相当の処理を行う例です。 コピー元・コピー先ブロックのポインタそれぞれからUnmanagedMemoryStreamインスタンスを作成し、CopyToメソッドでコピーを行います。
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))で紹介しています。