none
共用体を含んだ構造体を引数にとるDLL関数の処理方法について RRS feed

  • 質問

  • アンマネージDLL関数の引数に、下記のような、共用体を含んだ構造体をとるものがあるのですが、
    このような場合の構造体のマーシャリング方法がわかりません。
    typedef struct {
        int a;
        union {
            int  b;
            char *c;
        } value;
    } KYOUYOU;

    下記のようにしてみましたが、実行時に例外となってしまいます。
    (System.TypeLoadException はハンドルされませんでした。
     Message="アセンブリ 'kyouyoutai, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' からの型 'kyouyoutai.KYOUYOU' を読み込めませんでした。 オフセット 4 に不適切に整列されたか、

    オブジェクト以外のフィールドでオーバーラップされたオブジェクト フィールドが含まれています。")

    <StructLayout(LayoutKind.Explicit)> _
    Private Structure KYOUYOU
        <FieldOffset(0)> Dim a As Integer
        <FieldOffset(4)> Dim b As Integer
        <FieldOffset(4)> Dim c As String
    End Structure

    Integer型とString型を同じ境界に配置することはできないのでしょうか。
    このような構造体を引数にとるDLL関数を適切に処理するにはどのようにすればよいのでしょうか。
    ご存知の方がいらっしゃればアドバイスお願いいたします。

    2006年5月29日 7:16

回答

  • Hongliangさん、お答えありがとうございます。
    こんな便利なメソッドがあるんですね。
    下記のようにして、RtlMoveMemoryを使わずに文字列を取り出すことができました。

    Dim str As String = Marshal.PtrToStringAnsi(c)

    呼び出し先でのメモリ確保に関してですが、Hongliangさんのおっしゃるとおり、
    呼び出し側でメモリ確保が行われている模様です。
    使用しているDLLに、メモリ開放用関数が用意されていて
    値取得後はその関数でメモリの解放を行うというというのが作法のようです。

    アドバイスいただき、どうもありがとうございました。

    2006年5月30日 4:12

すべての返信

  • 参照型と値型を同じ(重なり得る)オフセットに配置した時に発生する例外ですね。マーシャル時に参照はポインタに解決されるとは言え両者は別物ですから、それを認めると色々困った事が発生するんでしょう。

    今回の質問くらいなら、String の代わりに IntPtr を使用して、IntPtr 越しにやり取りするのが良いでしょう。

    // 最近、同じような問題で悩んだ経験があります。そのときは固定長の Byte 配列として扱うという方法で逃げました。

    2006年5月29日 7:46
  • Hongliangさん、回答ありがとうございます。
    Hongliangさんのアドバイスをもとに上記構造体のメンバcを
    下記のようにString型からIntPtr型へ変更することで、例外とならなくなりました。

    <StructLayout(LayoutKind.Explicit)> _
    Private Structure KYOUYOU
        <FieldOffset(0)> Dim a As Integer
        <FieldOffset(4)> Dim b As Integer
        <FieldOffset(4)> Dim c As IntPtr
    End Structure

    この後、取得したcの先の文字列を取り出したい場合は、下記のようにRtlMoveMemoryを用いて
    確保したByte型配列へコピーを行うことで解決できました。ありがとうございました。
    Dim str(1023) As Byte
    RtlMoveMemory(str(0), c, ...)

    ただ、もっと直感的に下記のようなイメージでString型へ変換できるようにも思えるのですが、
    よい手段が見つかりません。もしRtlMoveMemoryを用いずにIntPtrをString型へ変換する方法をご存知でしたら
    お教えください。このような方法では実現できないのでしょうか。

    Dim str As String = CType(c, String)
    ※これだとstrはcの内容(アドレス)がそのまま文字列として表現されてしまう
     目的はそのアドレスに格納されている文字列をstrが指すようにしたい

    2006年5月30日 2:47
  • System.Runtime.InteropServices 名前空間 Marshal クラスの PtrToString... メソッドのどれかを使えばいいでしょう。char* なら Ansi かな。

    // 呼び出し先でメモリを確保してるっぽいけど、解放は大丈夫なのかな……。

    2006年5月30日 2:57
  • Hongliangさん、お答えありがとうございます。
    こんな便利なメソッドがあるんですね。
    下記のようにして、RtlMoveMemoryを使わずに文字列を取り出すことができました。

    Dim str As String = Marshal.PtrToStringAnsi(c)

    呼び出し先でのメモリ確保に関してですが、Hongliangさんのおっしゃるとおり、
    呼び出し側でメモリ確保が行われている模様です。
    使用しているDLLに、メモリ開放用関数が用意されていて
    値取得後はその関数でメモリの解放を行うというというのが作法のようです。

    アドバイスいただき、どうもありがとうございました。

    2006年5月30日 4:12