- - PR -
VB.NETからvoidポインタを引数にとるDLL関数を呼び出す方法
投稿者 | 投稿内容 | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2006-02-17 20:11
VB.NET2003でVC++6.0で作成されたDLLを使用する処理を作成しています。
このDLL内の関数にvoidポインタを引数にとる関数があります。 (VB6.0では、この関数の Declare宣言で「As Any」を指定していました。) この関数に対応するため、Declare宣言で <MarshalAs(UnmanagedType.AsAny)> を使用しているのですが、関数から戻った時に関数内で変更された内容が反映されません。 (関数ないで値を変更しているのですが、関数呼び出し前と同じ値のままになっています。) 関数呼び出しの前後で Marshal.StructureToPtr()/Marshal.PtrToStructure() を使用して自前でマーシャリングする方法では正常に動作しています。 御存知の方がいらっしゃいましたら教えて下さい。 [環境] VisualStudio.NET2003、Windows2000 [詳細] ・DLLの構造体宣言 <StructLayout(LayoutKind.Sequential, Pack:=4, CharSet:=CharSet.Auto)> _ Structure DLLSTRUCT2 Dim dummy As Integer End Structure ・DLLの関数宣言 Declare Function fnDllProc2B Lib "DllProc" Alias "_fnDllProc2@4" (ByVal ptr As IntPtr) As Integer Declare Function fnDllProc2C Lib "DllProc" Alias "_fnDllProc2@4" (<MarshalAs(UnmanagedType.AsAny)> ByVal ptr As Object) As Integer ・処理 ' 明示的にマーシャリング処理の呼び出し(正常) Dim dpr2b As DLLSTRUCT2 Dim p_dpr2b As IntPtr dpr2b.dummy = 100006 p_dpr2b = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(DLLSTRUCT2))) Marshal.StructureToPtr(dpr2b, p_dpr2b, False) fnDllProc2B(p_dpr2b) dpr2b = CType(Marshal.PtrToStructure(p_dpr2b, GetType(DLLSTRUCT2)), DLLSTRUCT2) Marshal.FreeHGlobal(p_dpr2b) ' <MarshalAs(UnmanagedType.AsAny)>を使用して暗黙的にマーシャリング。 ' DLLの関数には構造体が正しく渡っているが、関数内で変更された dpr2c が ' 正しく反映されていない。 ' (関数呼び出し前の値のままになっている。) Dim dpr2c As DLLSTRUCT2 dpr2c.array0.dummy = 100008 dpr2c.array1.dummy = 100009 fnDllProc2C(dpr2c) ところで、Marshal.AllocCoTaskMem() と Marshal.AllocHGlobal() の違いは何でしょうか。上記のようにマーシャリングに使用するのはどちらの方がいいでしょうか? 以上、よろしくお願いいたします。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-17 22:15
構造体のポインタを渡したいのなら、ByRefで宣言すればいいです。
Declare Function fnDllProc2C Lib "DllProc" (ByRef ptr As DLLSTRUCT2) As Integer ただ難点はこの場合NULLポインタを渡せないってところですが。 NULLも渡しうる場合、大抵はByValなIntPtrで定義したオーバーロードも宣言します。 この問題を克服するための一つの手段は、DLLSTRUCTをClass(参照型)とし、StructLayout属性でSequentialを指定することによって、(ByValで)このオブジェクトのポインタが渡されるようになります。 この場合はNothingがNULLポインタを指すようになります。 ただ、値渡しができなくなる点など、気を付ける問題は色々出てきます。
CoTaskMemはCOMの通信のために用意されたものなので、多少なりと単純にメモリを確保するHGlobalよりはリッチな実装になっていると考えればいいかと。 P/Invokeにおいて、こちらでAlloc/Free両方する分には、あまり意識する必要もないと思いますが。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-17 22:54
早速の回答ありがとうございます。
関数の引数は void* なので、いろいろな構造体を渡す必要があります。 (数+種類の構造体を渡します。) 質問では、単純化するために省略してしまったのですが、本来は構造体の種類を渡す引数があります。 尚、質問には記述していなかったのですが、ご指摘の方法でなら問題になっている「関数から戻った時に関数内で変更された内容が反映されない」現象が起きない事を確認しています。 構造体毎に関数をオーバーライドさせればできそうかとも思ったのですが、数+種類もあるとメンテナンスの面でも手間の面でもあまりやりたくありませんでしたので今回の質問をしました。分かり難くててすみません。
HGlobalの方を使うようにします。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-17 23:57
.NET の場合、構造体を扱う場合はボクシング/アンボクシングが発生したときに値のコピーが行われるので、そういう用途でVB.NETやC#を使うのはお勧めしないのですが。
Objectを受け取りDllImport先で操作し結果を返す、と言う場合、
と言う風に In 属性と Out 属性も必要になります。 // 参照型の場合自動的にin/outのはずなんですが、AsAnyの場合なぜかinだけになりますね。 もう一つの問題は初めに言ったボクシングによる値コピーの問題です。 引数がObjectですから、渡すときにボクシングが発生し、与えた構造体とは別のインスタンスがアンマネージドに渡されることになります。 これを回避するには、利用者が明示的にObjectに事前にボクシングしておき、関数呼び出しが終わってからアンボクシングを行う必要があります。
いちいち利用者にこれを要求するのは、オーバーロードを定義するよりもよほどメンテナンス性その他で問題があると思います。 もちろんし忘れたら呼び出し先での変更を受け取れず、やっかいなバグの元になるでしょう。 使用する構造体を全てClassで定義する(StructLayout属性を付加して)、と言う手もあります。このときはボクシング/アンボクシングが無いためごく自然に書けます。 誤ってStructureが混じってしまった場合はやっかいなバグになりそうですが。 次善の策としては、ラッパメソッドを用意するというのもありですね。
これなら利用者も返値を代入し直す必要がある点に気付きやすいでしょう。 // 個人的にはそんなのはManaged C++にさせるようにしますけど。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-18 10:08
利用者側がややこしくなるのはた避けたいので、最初に御提案頂いた方法で全構造体分のオーバーロードを定義する事にした方がよさそうですね。
とはどういうことでしょうか? (Managed C++でラッパーを作成するのでしょうか?) すみませんが教えて下さい。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-18 12:43
そういうことです。 C#/VBからは構造体を意識しないで扱えるような範囲で。 | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-18 13:58
もう少し教えて下さい。
Managed C++でラッパーを作成するというのは、VBのラッパメソッドと同じロジックをManaged C++で作成するのでしょうか?
そうだとしたら、VBで作成しないで、Managed C++で作成するメリットは何でしょうか?又、この場合にVBからManaged C++で作成したラッパ(DLL?)を使用するにはどうするのでしょうか?(結局はオーバーロードを構造体毎に定義する事になるような気がするのですが) 後、勉強のため、昨日教えて頂いたIn 属性と Out 属性を指定する方法
を試してみたのですが、In 指定のところでコンパイルエラー「キーワードは識別子として有効ではありません。」になってしまいます。なぜでしょうか? | ||||||||||||||||||||||||||||
|
投稿日時: 2006-02-18 14:40
関数呼び出し部分だけをラップするのは全く意味がないでしょう。 ですから、
と書いたように、各種構造体をC#/VBから隠蔽します。 その関数呼び出しに限定されず、もう少し大きい部品単位でラップするわけです。 ことによっちゃラップ以上の範囲になるかもしれませんが。
あ、これは失礼。VB では In は予約語でしたね。 予約語を予約語の用途以外で使う場合、[In] と言う風に書いてください。InAttribute でもいいですけど。 |