ポインタの加減算・マネージ配列のポインタ取得

※追記:.NET Framework4以降における方法 http://schima.hatenablog.com/entry/2013/10/08/003445

現在OpenCvSharpをC#メインで書き直し中です。C++/CLIに比べるとVisualStudioの支援が手厚く、コンパイルも圧倒的に速いので作業効率は良いです。

しかしラッパーというのは結局のところポインタとの終わりなき戦いであり、そのあたりを.NET FrameworkのIntPtrだけで戦っていくのはなかなか厳しいものです。その分、C++/CLIは困ったら本物のポインタを持ち出せばよいのでどうにでもなります。

で、今回はポインタのインクリメントといった初歩の初歩で少々考えてしまったのでメモ。ついでにマネージ配列からポインタへの変換についてもまとめておきます。

プリミティブ型の場合

こんなCのプログラムがあったとします。

int ary[] = {1,2,3,4,5};

int* ptr = &ary[0];

ptr += 2;  // これがやりたい!

int value = *ptr;  // = 3

このptr += 2;というところをIntPtrでどうやるのかというお話です。頼みの綱のMarshalには特になさそうだったので、真っ当にIntPtrのアドレスを sizeof(int)*2 ずらすことになるかと思います。

ptr = new IntPtr(ptr.ToInt32() + sizeof(int) * 2); 


ちなみに、最初のCのプログラムをすべてC#に書き換えるとこうなるでしょう。

int[] ary = {1,2,3,4,5};

// 配列をピン止めしてGCの対象から外す
GCHandle gch = GCHandle.Alloc(ary, GCHandleType.Pinned);

// 配列の先頭のアドレスを取得
IntPtr ptr = gch.AddrOfPinnedObject();

// ポインタの加算
ptr = new IntPtr(ptr.ToInt32() + sizeof(int) * 2); 

// 値の読み取り
int v = Marshal.ReadInt32(ptr);

// ピン止め解除
gch.Free();

GCHandleがミソ。C++/CLIでいうとpin_ptrに近いです。

非プリミティブ型の場合

上記のようなプリミティブの配列の場合はまだ初歩中の初歩、今度はintではなくTのマーシャリングのお話です。Tはwhere T : struct であるとします。

T Read3rd<T>(T[] array) where T : struct
{
    GCHandle gch = GCHandle.Alloc(array, GCHandleType.Pinned);
    IntPtr ptr = gch.AddrOfPinnedObject();

    // ポインタの加算
    ptr = new IntPtr(ptr.ToInt32() + Marshal.SizeOf(typeof(T)) * 2); 

    // 値の読み取り
    T v = (T)Marshal.PtrToStructure(ptr, typeof(T));

    gch.Free();

    return v;
}

ピン止めできるのはblittableな型のメンバのみを持つ構造体に限られます。blittableかそうでないかについては、以下のページを参照してください。
http://msdn.microsoft.com/ja-jp/library/75dwhxf7(VS.80).aspx
where句にこの制約条件が書けると良いのですが、なさそうです。


使用例はこちら。

int[] ary = {1,2,3,4,5};
Point[] pts = { new Point(30, 40), new Point(123, 456), new Point(777,777) };

Console.WriteLine(Read3rd(ary));  // => 3
Console.WriteLine(Read3rd(pts));  // => {X=777,Y=777}