構造体とバイト配列の相互変換を行う方法、およびBinaryReader・BinaryWriterで構造体の読み書きを行う方法について。
.NET Frameworkでは任意の構造体とバイト配列を相互に変換するクラスやメソッドが用意されていない。 また、Stream・BinaryReader・BinaryWriterなどのクラスも直接構造体の読み書きを行う方法をサポートしていない。 そのため、以下で紹介するような方法を使って独自に実装する必要がある。
Marshal.AllocHGlobal + Marshal.StructureToPtr/PtrToStructure
この方法では、Marshal.AllocHGlobalを使ってバイト配列の読み書きを行う領域を作成し、Marshal.Copyで構造体のバイト表現をコピーする。 コピーに際して、Marshal.StructureToPtrおよびMarshal.PtrToStructureで構造体とポインタを相互に変換する。
Marshal.GCAlloc + Marshal.StructureToPtr/PtrToStructure
この方法では、Marshal.AllocHGlobalを使うかわりにGCHandle.Allocを使うことで直接バイト配列のポインタを取得し、Marshal.PtrToStructure・Marshal.StructureToPtrで構造体に変換する。
ポインタのキャスト・代入
この方法では、Marshalクラスのメソッドは使わず、直接ポインタを操作してバイト配列と構造体の変換を行う。
(VBではポインタが使用できないためこの方法は使えない)
BinaryFormatterによるシリアライズ・デシリアライズ
この方法では、BianryFormatterでシリアライズすることで構造体のバイト表現を取得する。
この例で使用しているBinaryFormatter.Serialize/Deserializeについて、.NET 5以降においては使用は推奨されず、できるだけ早く使用をやめる必要があるとされています。 以下のコードでは、これを示すコンパイル時警告SYSLIB0011が出力されます。 特にASP.NET 5.0以降では、明示的にBinaryFormatterの使用を有効にしない限り常に例外NotSupportedExceptionがスローされます。
BinaryFormatter 型は危険であり、データ処理用としては "推奨されません"。 アプリケーションでは、処理するデータが信頼できると思われる場合でも、できるだけ早く BinaryFormatter の使用をやめる必要があります。 BinaryFormatter は安全ではなく、セキュリティで保護することはできません。
BinaryFormatter セキュリティ ガイド | Microsoft Docs
BinaryFormatter シリアル化メソッドが古い形式になり、ASP.NET アプリでは使用不可に
BinaryFormatter、Formatter、および IFormatter の Serialize と Deserialize のメソッドが古いと見なされ、警告が示されるようになりました。 また、ASP.NET アプリでは、BinaryFormatter のシリアル化が既定で禁止されます。
変更の説明
BinaryFormatter のセキュリティ脆弱性により、次のメソッドは古いと見なされ、ID SYSLIB0011 のコンパイル時警告が生成されるようになりました。 また、ASP.NET Core 5.0 以降のアプリでは、Web アプリによって BinaryFormatter 機能が再有効化されていない限り、NotSupportedException がスローされます。
基本クラス ライブラリの破壊的変更 - .NET Core | Microsoft Docs
- BinaryFormatter.Serialize
- BinaryFormatter.Deserialize
これに従い、シリアライズによる構造体とバイト配列の相互変換は、ASP.NET 5.0以降では明示的に有効にしない限り使用できない手段で、またそれ以外のフレームワークでも推奨できる手段ではなくなっています。
リフレクション
この方法では、構造体のバイト表現を直接取得するのではなく、構造体のフィールドをリフレクションによって取得し、フィールドに格納されている値を個別にバイト配列へと変換していく。 つまり、構造体の全フィールドを一つずつ読み書きする方法と同じで、それをリフレクションによって汎用化している。
- ReadWriteStructWithReflection.cs (構造体からバイト配列を出力できたらどれだけ楽だろうか - test)
- 上記サンプルを拡張して、配列フィールド・文字列フィールド・入れ子になった構造体フィールドの書き込み、エンディアンを指定した書き込みをサポートしたもの
使用例
ここまでで紹介した方法を実際に使用する例。