雑な引数付きでネイティブ DLL の関数を呼び出したい,の巻
2003/11/17 新規

以下のサイトも参考にしてください。

 

C/C++ 言語で作られた DLL の関数に引数として構造体を渡すとき、 その構造体のメンバに構造体ポインタが含まれていた場合どうすれば良いでしょうか?

ポイントは

  1. 構造体ポインタは IntPtr 構造体で扱うこと
  2. 事前に必要サイズ分だけメモリ確保すること
  3. IntPtr 構造体から目的の構造体配列へはマーシャリングできないので、個々の構造体として取り出すこと
  4. 使い終わったら確保したメモリを開放すること
という感じです。
以下に、サンプルを示します。

STRUCT_A 構造体は以下のように、別の構造体へのポインタをメンバとして持ちます。
(C 言語)

struct STRUCT_A
{
    (略)
    unsigned int cnt;
    STRUCT_B* lst;
    (略)
};

struct STRUCT_B
{
    (略)
};
上の構造体を C# で書き直すと、こうなります。
(C# 言語)

[StructLayout(LayoutKind.Sequential,Pack=4)]
public struct STRUCT_A
{
    (略)
    public uint cnt;
    public IntPtr lst;
    (略)
};

[StructLayout(LayoutKind.Sequential,Pack=4)]
public struct STRUCT_B
{
    (略)
};

このような構造体を、C 言語で作成された DLL の関数へ引数として渡すには次のようにします。
まず、引数として渡すための変数を result として用意します。 その次にメンバ内の構造体ポインタについては、Marshal クラスを用いてメモリを確保します。 確保すべきサイズ(配列のインデクス数)は、既に知っていると仮定。
例1

STRUCT_A result=new STRUCT_A();
IntPtr lst_p;

// 不定長情報のメモリ確保
lst_p=Marshal.AllocCoTaskMem(Marshal.SizeOf(new STRUCT_B())*(int)result.cnt);
result.lst=lst_p;

そして、DLL 関数をコールします。 変数 result は、ref キーワードで参照渡しです。
例2

// 外部 DLL 関数コール
dll_func(ref result);

構造体ポインタは、構造体の配列としては認識(マーシャリング)できないので、 ポインタのオフセット値をずらしながら、1つずつ STRUCT_B 構造体の値を取得します。
例3

STRUCT_B[] lstArray=new STRUCT_B[result.cnt];
IntPtr lstPointer=result.lst;

for(int i=0;i<result.cnt;i++)
{
    lstArray[i]=new STRUCT_B();
    lstArray[i]=(STRUCT_B)Marshal.PtrToStructure(lstPointer,typeof(STRUCT_B));
    lstPointer=(IntPtr)((int)lstPointer+Marshal.SizeOf(lstArray[i]));
}

あとは構造体配列 lstArray を使って、目的の値を使用します。 使い終わったら、最初に確保したメモリ領域を開放するの忘れずに!
例4

Marshal.FreeCoTaskMem(lst_p);


補足)
DLL に渡す構造体のメンバに構造体ポインタが含まれていた場合、と書きましたが。 実際には ともかくポインタがあったら、IntPtr 構造体で扱うのが良いようです。 例外なのは、サイズが分かる char[] 型だけだと思います。
(C 言語)

struct STRUCT_C
{
    int bufSize;
    char* buf;
    char bufArray[5];
};
これを直すと、こうなります。
(C# 言語)

[StructLayout(LayoutKind.Sequential)]
public struct STRUCT_C
{
    public int bufSize;
    public IntPtr buf;
    [MarshalAs(UnmanagedType.ByValTStr,SizeConst=5)]
    public string bufArray;
};

この構造体メンバの bufArray のようなサイズのわかってる配列なら、Marshal属性でそのサイズを設定して string クラスで扱えます。 それ以外のポインタは、int 型だろうが string クラスだろうが構造体だろうが、動的なメモリ確保/開放をする処理が必須である気がします。

そのときに、メモリ確保はどんな型でも Marshal.AllocCoTaskMem() メソッドで行えます。 そして int 型(UInt32 構造体)や構造体であれば Marshal.PtrToStructure() を使い、 string クラスであれば Marshal.PtrToStringAnsi() を使って IntPtr 構造体から変換できます。


戻る