ポインタ(バイト配列)から構造体への変換

前回の記事の逆変換の場合を考えていきます。基本的に、ポインタに変換するよりはコーディングは楽です。
なお、今回変換する構造体と、その構造体のデータが入ったポインタ(IntPtr)は以下のようなものとします。

[StructLayout(LayoutKind.Sequential)]
struct Fuga
{
    public double A;
    public double B;
    public double C;
}
Fuga fuga = new Fuga { A = 1.23, B = 4.56, C = 7.89 };
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(fuga));
Marshal.StructureToPtr(fuga, ptr, false);

なおバイト配列の場合は、Marshal.Copyするなり、GCHandleでピン止めするなりすればポインタとして扱えるので、今回は割愛します。

Test1 : Marshal.PtrToStructure

一番オーソドックスな方法。

Fuga obj = (Fuga)Marshal.PtrToStructure(ptr, typeof(Fuga));

Test2 : CopyMemory

Win32APIのCopyMemoryDllImportで定義した上で、それを使ってメモリのコピーを行います。

[DllImport("kernel32.dll")]
static extern void CopyMemory(IntPtr dst, IntPtr src, int size);
Fuga obj = new Fuga();
GCHandle gch = GCHandle.Alloc(obj, GCHandleType.Pinned);
CopyMemory(gch.AddrOfPinnedObject(), ptr, Marshal.SizeOf(obj));
obj = (Fuga)gch.Target;
gch.Free();

Fugaは値型のため、GCHandleに渡した時点で値渡しされて別物になってしまいます。ですからCopyMemoryした後にその結果を代入し直しています。

ちなみにfixedを使うと3倍ぐらい速くなりますが、今回はいろいろ疲れてるので割愛。というかわざわざunsafeにしてそれをするぐらいなら、下記のように直接代入したほうが速いですので。

Test3 : ポインタ

ポインタ演算で直接コピーします。わずか1行、いかにも速そう。

unsafe
{
    Fuga obj = *(Fuga*)ptr;
}

処理速度

上記の方法それぞれをループで777777回実行したときの処理速度を測ってみました。

手法 処理時間(ms)
Marshal.StructureToPtr 324
CopyMemory 306
ポインタ 16

感想

  • それにしてもやっぱりunsafeは速い
  • 何度やっても大抵CopyMemoryの方がStructureToPtrよりわずかに速い
  • 前回の記事ではsizeof(Hoge)は8でStructureToPtrの処理時間は200msぐらいだったのに、今回sizeof(Fuga)は24で処理時間は300ms強。PtrToStructureの方が処理は速そう

ということで、前回と同じ結論ですが、とことん速さを追求したければポインタを使えばいいし、汎用的なコードを書きたいとか特に速さはこだわらないというのであれば、おとなしくMarshal.PtrToStructureで良いと思います。

最後に

今回のテストに使ったコードを載せておきます。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PtrToStructureTest
{
    [StructLayout(LayoutKind.Sequential)]
    struct Fuga
    {
        public double A;
        public double B;
        public double C;
    }

    class Program
    {
        [DllImport("kernel32.dll")]
        static extern void CopyMemory(IntPtr dst, IntPtr src, int size);

        const int Count = 777777;
        static IntPtr ptr;

        static Program()
        {
            Fuga fuga = new Fuga { A = 1.23, B = 4.56, C = 7.89 };
            ptr = Marshal.AllocHGlobal(Marshal.SizeOf(fuga));
            Marshal.StructureToPtr(fuga, ptr, false);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Test1_Pointer     : {0}ms", MeasureTime(Test1_Pointer).Milliseconds);
            Console.WriteLine("Test2_Pointer     : {0}ms", MeasureTime(Test2_Pointer).Milliseconds);
            Console.WriteLine("Test3_Pointer     : {0}ms", MeasureTime(Test3_Pointer).Milliseconds);
            Console.Read();
        }

        static void Test1_Pointer()
        {
            Fuga obj = (Fuga)Marshal.PtrToStructure(ptr, typeof(Fuga));
        }
        static void Test2_Pointer()
        {
            Fuga obj = new Fuga();
            GCHandle gch = GCHandle.Alloc(obj, GCHandleType.Pinned);
            CopyMemory(gch.AddrOfPinnedObject(), ptr, Marshal.SizeOf(obj));
            obj = (Fuga)gch.Target;
            gch.Free();
        }
        static unsafe void Test3_Pointer()
        {
            Fuga obj = *(Fuga*)ptr;
        }

        /// <summary>
        /// 指定したデリゲートの実行にかかった時間を計測
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        static TimeSpan MeasureTime(Action action)
        {
            Stopwatch watch = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                action();
            }
            watch.Stop();
            return watch.Elapsed;
        }
    }
}