スキップしてメイン コンテンツに移動

C#でMarshalクラスを使ってbyte配列から構造体を生成する方法

C#の通信プログラムでbyte配列を読み込んで、構造体(struct)を生成したいときにサンプルコードの紹介です。
Genericsを使って任意の構造体をbyte配列から生成できるようにしています。

public static T ReadAsStruct<T>(this byte[] bytes, int startIndex) where T : struct  
{  
    // GCHandle.Allocクラスを使ってメモリ上のデータがガーベッジコレクタの対象にならないようにする。
    var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);  
    try  
  {  
        // Marshalクラスのメソッドを使って、byte配列のIntPtr(ポインタ)を取得。
        var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, startIndex);
        // IntPtrで指定した位置のbyte配列から構造体を生成。
        return (T)Marshal.PtrToStructure(ptr, typeof(T));  
    }  
    finally  
    {  
        // 重要!!必ずGCHandleを開放して、再びbyte配列がガーベッジコレクタの対象になるようにする。
        handle.Free();  
    }  
}

ちょっと考察

■ パフォーマンス

毎回GCHandle.Alloc, Freeを行う分、パフォーマンスのオーバーヘッドが発生します。パフォーマンスが気になる場合は、GCHandle.Alloc, Freeの間に、複数の構造体を生成するようにするとよいかもしれません。

■ 他の書き方

startIndexがいつもゼロの場合、下記の部分は、

var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, startIndex);
return (T)Marshal.PtrToStructure(ptr, typeof(T));  

return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));

のように書くこともできます。

■ unsafeを使ったバージョン

unsafe T ReadAsStruct<T>(byte[] bytes, int startIndex) where T : struct
{
    fixed (byte* ptr = &bytes[startIndex])
    {
        return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
    }
}

■ Marshalを使う以外の方法

こちらの記事にきれいにまとまっていました。

コメント