メンバに配列を持つ構造体のマーシャリング

コメントにご質問があったので、少し長くなりそうなのでこちらで書かせてもらいます。

構造体とポインタ(若しくはバイト配列)との相互変換を行う方法については以前紹介しましたが、あの時の例はあえてごく簡単な構造体にしてありました。今回はメンバとして配列をもっている構造体の場合について考えます。ここでは、以下のような構造体を例にとりご説明します。

[StructLayout(LayoutKind.Sequential)]
struct Hoge
{
    public string Str;
    public double[] Array;
    public Fuga[] Fugafuga;
}

[StructLayout(LayoutKind.Sequential)]
struct Fuga
{
    public int X;
    public int Y;
    public Fuga(int x, int y) { X = x; Y = y; }
}

Hogeは、文字列(つまり、charの配列)と、doubleの配列、そして構造体Fugaの配列を持っています。Fugaはblittableでなければなりません。

MarshalAsAttribute

C#では、Cと違って定義だけでは配列の長さがわかりません。そこでMarshalAsを使い、Hogeの定義を次のように書き換えます。

[StructLayout(LayoutKind.Sequential)]
struct Hoge
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string Str;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public double[] Array;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public Fuga[] Fugafuga;
}

SizeConstに指定する定数は、C/C++側のヘッダ定義を見て、適宜設定します。

この構造体の定義さえしっかりやってしまえば、後は特別な処理は要りません。もっと複雑な場合はうまくいかないこともあるのですが、その場合はC++/CLIの利用も視野に入れるべきかもしれません。

構造体からポインタへ

Marshal.StructureToPtrを使います。特に変わったところはありません。

static IntPtr ToPtr(Hoge obj)
{
    IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Hoge)));
    Marshal.StructureToPtr(obj, ptr, false);
    return ptr;
}

構造体からバイト配列へ

バイト配列に変換したい場合は、ポインタへ変換後、Marhsal.Copyをするのが順当でしょう。

static byte[] ToBytes(Hoge obj)
{
    int size = Marshal.SizeOf(typeof(Hoge));
    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(obj, ptr, false);

    byte[] bytes = new byte[size];
    Marshal.Copy(ptr, bytes, 0, size);
    Marshal.FreeHGlobal(ptr);
    return bytes;
}

ポインタから構造体へ

Marshal.PtrToStructureを使います。

static Hoge ToStruct(IntPtr ptr)
{
    return (Hoge)Marshal.PtrToStructure(ptr, typeof(Hoge)); 
}

バイト配列から構造体へ

先ほどと同様にMarshal.Copyを使うこともできますが、ここではもう一つの方法としてGCHandleでバイト配列のポインタを取得し、それを用いてMarshal.PtrToStructureを行ってみます。

static Hoge ToStruct(byte[] bytes)
{
    GCHandle gch = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    Hoge result = (Hoge)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Hoge));
    gch.Free();
    return result;
}

まとめ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApplication1
{
    [StructLayout(LayoutKind.Sequential)]
    struct Hoge
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string Str;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
        public double[] Array;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public Fuga[] Fugafuga;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct Fuga
    {
        public int X;
        public int Y;
        public Fuga(int x, int y) { X = x; Y = y; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Hoge obj = new Hoge();
            obj.Str = "Hello world!";
            obj.Array = new double[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            obj.Fugafuga = new Fuga[3] { new Fuga(1, 2), new Fuga(3, 4), new Fuga(5, 6) };

            // 構造体 <-> ポインタ
            IntPtr ptr = ToPtr(obj);
            Hoge obj2 = ToStruct(ptr);
            Marshal.FreeHGlobal(ptr);

            // 構造体 <-> バイト配列
            byte[] bytes = ToBytes(obj);
            Hoge obj3 = ToStruct(bytes);

            // 適当なところでブレークポイントを作って、
            // obj2, obj3の中身を見てみるべし
            Console.Read();
        }

        static IntPtr ToPtr(Hoge obj)
        {
            int size = Marshal.SizeOf(typeof(Hoge));
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, false);
            return ptr;
        }
        static byte[] ToBytes(Hoge obj)
        {
            int size = Marshal.SizeOf(typeof(Hoge));
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, false);
            byte[] bytes = new byte[size];
            Marshal.Copy(ptr, bytes, 0, size);
            Marshal.FreeHGlobal(ptr);
            return bytes;
        }

        static Hoge ToStruct(IntPtr ptr)
        {
           return (Hoge)Marshal.PtrToStructure(ptr, typeof(Hoge)); 
        }
        static Hoge ToStruct(byte[] bytes)
        {
            GCHandle gch = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            Hoge result = (Hoge)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Hoge));
            gch.Free();
            return result;
        }
    }
}