前回の記事の逆変換の場合を考えていきます。基本的に、ポインタに変換するよりはコーディングは楽です。
なお、今回変換する構造体と、その構造体のデータが入ったポインタ(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のCopyMemoryをDllImportで定義した上で、それを使ってメモリのコピーを行います。
[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; } } }