delete from hateblo.jp where 1=1;

タイトルに意味はありません。

構造体からバイト配列を出力できたらどれだけ楽だろうか

対象

以下のマニアックな人向けの資料。

  • バイト配列をなんとか構造を元に出力したい
  • だが、Little Endianに限られて詰んでいる

検証

Marshal.AllocHGlobal + Marshal.StructureToPtr/PtrToStructure

下記のような定義があるとする。

[StructLayout(LayoutKind.Sequential)]
struct TestStructure
{
    public byte param1;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
    public byte[] array1;
    public int param2;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public int[] array2;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string param3;
    [MarshalAs(UnmanagedType.Struct)]
    public TestStructureInternal param4;
}
[StructLayout(LayoutKind.Sequential)]
struct TestStructureInternal
{
    public int next;
    public char chr;
}


public partial class Form1 : Form
{
    private void button1_Click(object sender, EventArgs e)
    {
        var item = new TestStructure();
        //01 
        item.param1 = (byte)1;
        //0a 0b 0c 
        item.array1 = new byte[] {0x0a,0x0b,0x0c};
        //02 00 00 00
        item.param2 = (int)2;
        //ff 00 00 00 00 01 00 00
        item.array2 = new int[] { 0x00ff, 0x0100 };
        //74 65 73 00  tes. ←MarshalAsでSizeConstに4を指定している関係でNull終端が設定されたためtが欠落しているものと思われる.ByValTStrの指定が悪いのか原因不明。
        item.param3 = "test";
        item.param4 = new TestStructureInternal();
        //06 00 00 00
        item.param4.next = (int)6;
        //61
        item.param4.chr = 'a';
        //00 00 00 ←おそらくバウンダリのための空白
        
        string filename = "outfile.out";
        if (File.Exists(filename))
        {
            File.Delete(filename);
        }
        using (var file = new FileStream(filename, FileMode.CreateNew))
        using (var writer = new BinaryWriter(file))
        {
            ReadWriteStructWithAllocHGlobal.WriteTo(writer, item);
        }
        
    }
}

実行結果は以下のようになった。わかりやすいように上記コードにもダンプを貼り付けておく。

00000000  01 0a 0b 0c 02 00 00 00  ff 00 00 00 00 01 00 00  |................|
00000010  74 65 73 00 06 00 00 00  61 00 00 00              |tes.....a...|

解決策

このままでは実装しても使い物にならない(文字列の終端NULLが勝手に挿入される)ので、カスタマイズで実装してみた。
https://gist.github.com/indication/6b8d85c7da8cf6fd8f5c#file-readwritestructwithreflection-cs

Structure→byte[]はできたが、逆はまだ実装できていない。
必要に応じて拡張する予定。
BigEndianにも対応してみたが、「BitConverter.IsLittleEndian」のフラグを参照していないので、考慮に入れてみる。

isBigEndian=falseの場合の結果
00000000  01 0a 0b 0c 02 00 00 00  ff 00 00 00 00 01 00 00  |................|
00000010  74 65 73 74 06 00 00 00  61 00                    |test....a.|

isBigEndian=trueの場合の結果
00000000  01 0a 0b 0c 00 00 00 02  00 00 00 ff 00 00 01 00  |................|
00000010  74 65 73 74 00 00 00 06  00 61                    |test.....a|

ライセンス

上記検証コードについては、BinaryReader・BinaryWriterでの構造体の読み書き (構造体⇔バイト配列の変換) - Programming/.NET Framework/Tips - 総武ソフトウェア推進所 の規約に基づくものとし、私が記載したものはWTFPL Version 2.0とします。

参考文献