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));
}
}
コメント