C#でUnmanagedな構造体へのポインタを

FFTWのDLLをC#から利用しようとWrapperを書いているときに思いついた。


Cで書かれたDLL内の関数に構造体へのポインタが使われていると、C#でインポートするにはものすごく困ってしまう。たとえばFFTWにでてくるこのような関数だ

fftw_plan fftw_plan_dft_1d(int n, fftw_complex *in, fftw_complex *out, int sign, unsigned flags);

fftw_complex*はdouble[]を使うことで代用できるので解決される。問題はfftw_planという型で、これはfftw_plan_sという構造体へのポインタ型となっている。


Cを良く知っている人ならば型名の後に*をつければいいのでは?と言うだろうが、C#はそうは問屋がおろさない。C#ではunsafeという括弧をつけたブロック内では*が付いたポインタを確かに利用することができる。しかし、それはbyteやdoubleなどの元からある値型に限られ、自分で書いた構造体に対してはコンパイルが通らない。

IntPtrを用いる

そこで妥協案として、普通ポインタの部分はIntPtrというポインタを記憶する型を当てることが多い。すなわちC#側では

[DllImport("libfftw3-3.dll", EntryPoint = "fftw_plan_dft_1d")]
public static extern IntPtr PlanDFT1D(int Length, double[] In, double[] Out, int Sign, uint Flags);

確かにこれでコンパイルは通る。。。しかし、IntPtrが指すメモリ領域のデータを読み書きするには

[StructLayout(LayoutKind.Sequential)]
struct plan_s
{
    public IntPtr adt;
    public opcnt ops;
    public double pcost;
    public wakefulness wakefulness; /* used for debugging only */
    public int could_prune_now_p;
}

などと構造体を宣言してMarshalクラスを毎回呼んでデータを読み書きをするかGCHandleを使うかなどしないといけない。



改善策はどうやら二つあるようだ。

同じ構造を持つクラスを用意する

1つ目の方法は構造体と同じメンバーと構造を持つクラスを書いてあげる。
たとえば先ほどのplan_sならば

[StructLayout(LayoutKind.Sequential)]
class plan
{
    public IntPtr adt;
    public opcnt ops;
    public double pcost;
    public wakefulness wakefulness; /* used for debugging only */
    public int could_prune_now_p;
}

とする。そしてインポート部は

[DllImport("libfftw3-3.dll", EntryPoint = "fftw_plan_dft_1d")]
public static extern plan PlanDFT1D(int Length, double[] In, double[] Out, int Sign, uint Flags);

とする。これでうまくいくようだ。そこでさらに内部の変数IntPtr adtに対しても同じ事を行ったのだが、AccessViolationExceptionが発生してしまう・・・一体なぜ??
よくわからないのでこの方法は諦めることにした。

構造体へのポインタ型を作る汎用型を作る

IntPtrを拡張した値型を作れないだろうかとひらめいた。つまりこのような汎用型を作る。

[StructLayout(LayoutKind.Sequential)]
struct IntPtr<Structure> where Structure : struct
{
    public readonly IntPtr Pointer;
    public Structure Value
    {
        get
        {
            return (Structure)Marshal.PtrToStructure(this.Pointer, typeof(Structure));
        }
        set
        {
            Marshal.StructureToPtr(value, this.Pointer, true);
        }
    }
}

そしてインポートは

[DllImport("libfftw3-3.dll", EntryPoint = "fftw_plan_dft_1d")]
public static extern IntPtr<plan_s> PlanDFT1D(int Length, double[] In, double[] Out, int Sign, uint Flags);

とすれば戻り値の型は値型plan_sへのポインタを記録する値型になる!(面白いのはポインタなのに値型になっている事。よく考えれば、ポインタと言うのは所詮数値なのだから実体は値型なのである。)


これを使ってFFTWをC#から使うことができた。落ち着いたらここに書くかもしれない。
ちなみにこの型名をつけるのに悩んだ(笑) Pointer<>だと長いし、p<>だとなんかC#っぽくないし、_<>だとC++/CLIのかの失敗例のようにかっこ悪いし…と悩みぬいた挙句ベースのIntPtrをパクルことにした。



ただ、やっぱりclassを使ってかけないのだろうかと思ってしまう。そのほうがスマートだと思うから…