経由でアプリケーションにアンマネージ メモリを割り当てていますMarshal.AllocHGlobal
。次に、一連のバイトをこの場所にコピーし、結果のメモリ セグメントを に変換してから、struct
を介してメモリを再度解放しますMarshal.FreeHGlobal
。
メソッドは次のとおりです。
public static T Deserialize<T>(byte[] messageBytes, int start, int length)
where T : struct
{
if (start + length > messageBytes.Length)
throw new ArgumentOutOfRangeException();
int typeSize = Marshal.SizeOf(typeof(T));
int bytesToCopy = Math.Min(typeSize, length);
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);
if (length < typeSize)
{
// Zero out additional bytes at the end of the struct
}
T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
Marshal.FreeHGlobal(targetBytes);
return item;
}
これはほとんどの場合機能しますが、struct
必要なサイズよりもバイト数が少ない場合、最後のフィールドに「ランダムな」値が割り当てられます(LayoutKind.Sequential
ターゲット構造体で使用しています)。これらのぶら下がっているフィールドをできるだけ効率的にゼロにしたいと思います。
コンテキストとして、このコードは Linux 上の C++ から送信された高頻度のマルチキャスト メッセージを逆シリアル化しています。
失敗したテスト ケースを次に示します。
// Give only one byte, which is too few for the struct
var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 });
Assert.AreEqual(0x21, s3.Byte);
Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct S3
{
public byte Byte;
public int Int;
}
このテストを繰り返し実行すると、2 番目のアサートが毎回異なる値で失敗します。
編集
最後に、私はleppie の提案である をunsafe
使用しstackalloc
ました。これにより、必要に応じてゼロに設定されたバイト配列が割り当てられ、メッセージ サイズに応じてスループットが 50% から 100% の間で改善されました (メッセージが大きいほどメリットが大きくなります)。
最終的な方法は次のようになりました。
public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length)
where T : struct
{
if (length <= 0)
throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero.");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero.");
if (startIndex + length > messageBytes.Length)
throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length");
int typeSize = Marshal.SizeOf(typeof(T));
unsafe
{
byte* basePtr = stackalloc byte[typeSize];
byte* b = basePtr;
int end = startIndex + Math.Min(length, typeSize);
for (int srcPos = startIndex; srcPos < end; srcPos++)
*b++ = messageBytes[srcPos];
return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T));
}
}
Marshal.PtrToStructure
残念ながら、これには、バイトをターゲット型に変換するための呼び出しが必要です。