- PR -

C言語で作成されたDLLをVB.NETにて呼び出す方法

投稿者投稿内容
ひさ
会議室デビュー日: 2006/02/09
投稿数: 6
投稿日時: 2006-02-09 19:08
初めまして、皆様よろしくお願いいたします。
初めて投稿させて頂きますので、不憫な点があるかも知れませんが、
よろしくお願いいたします。

件名の通り、C言語でコンパイルされたDLL内にある関数を
VB.NETにて利用しようとしています。

方法としては「System.Runtime.InteropServices」をインポートし
「DllImport」を利用すれば使えるようになると思っていたのですが、

「'System.Runtime.InteropServices.MarshalDirectiveException' の
ハンドルされていない例外が Test.exe で発生しました。
追加情報 : メソッドの型署名は PInvoke と互換していません。」
というエラーメッセージが出力されました。

これは、型が合っていない時に出力されるものと思っているのですが、
正しいと思っています。 かなり悩んだのですが、解決方法が出てこないので、
皆様のご教授をお願いしたいので、よろしくお願いいたします。

下記がそのソースコードです。

☆C言語で作成されたDLLの関数は戻り値として構造体を利用しています。
================================================================
C言語側で作成されたDLLの関数(仕様しか無いのでそれを記述します)
================================================================
○ACResult構造体
typedef struct ACResult
{
char A[7+1];
char B[8+1];
char C[20+1];
}

○登録されている関数
ACResult A(const char* A , const char* B , const char* C)

================================================================
DLLに設定された関数をVB.NETにて呼び出すソース================================================================
Imports System.Runtime.InteropServices

Public Class Test
Inherits System.Windows.Forms.Form

Structure ACResult
Public A As String
Public B As String
Public C As String
End Structure

<DllImport("C_TEST.dll")> _
Private Shared Function A_
(ByVal A As String, ByVal B As String, ByVal C As String ) As ACResult
End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim b As ACResult
b = A("1", "2", "3")  '←この箇所でException発生
Msgbox b.A

End Sub
End Class
----------------------------------------------------------
環境は、WindowsXP SP2、VB.NET2003です。

対象DLL「C_TEST.dll」に関しては、このファイルがある
ディレクトリに対してPATH設定しております。

ご教授願いたく、よろしくお願いいたします。
不足情報がありましたら、ご指摘願います。


[ メッセージ編集済み 編集者: hisa 編集日時 2006-02-09 19:10 ]

[ メッセージ編集済み 編集者: hisa 編集日時 2006-02-09 19:11 ]
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-02-09 19:38
引用:

方法としては「System.Runtime.InteropServices」をインポートし
「DllImport」を利用すれば使えるようになると思っていたのですが、


// VB伝統であるDeclare構文でも可能です。ま、たいした違いはありませんが。

引用:
コード:
typedef struct ACResult
{
    char A[7+1];
    char B[8+1];
    char C[20+1];
}



固定長配列を.NETで扱う場合、そのままでは固定長配列を表現する手段が基本的に存在しないので一手間必要になります。

引用:
コード:
ACResult A(const char* A , const char* B , const char* C)



// Cらしくない関数プロトタイプですね

引用:
コード:
Structure ACResult
    Public A As String
    Public B As String
    Public C As String
End Structure



さて、さっきも言ったとおりそのままStringで扱うのは無理です。Stringは参照型であり、Cでは
コード:
typedef struct {
    char *A;
    char *B;
    char *C;
} ACResult;


という構造体になってしまいます。
でどうすればいいか、って言うのは比較的簡単ですが、それ以外にもアンマネージドとの相互運用には色々落とし穴があったりするので、一度きっちり基礎から勉強すべきでしょう。
アンマネージ DLL 関数の処理
文字列のマーシャリング
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2006-02-09 20:01
こんばんは。

引用:

Hongliangさんの書き込み (2006-02-09 19:38) より:
引用:
コード:
ACResult A(const char* A , const char* B , const char* C)



// Cらしくない関数プロトタイプですね



私もそう思いました。
「戻り値の型が構造体」というところが。
これだと、呼び出し先からリターンするとき呼び出し元との間で
「構造体」->「構造体」の”コピー”が発生し、(若干ですが)実行効率を落とすので
C言語では「構造体ポインタ」でやり取りするのが通例だと思いますけれども…

こんな感じのほうが自然だと思います。
コード:
void A(/*[in]*/ const char* a , /*[in]*/ const char* b, /*[in]*/ const char* c, /*[out]*/ ACResult* result)


ひさ
会議室デビュー日: 2006/02/09
投稿数: 6
投稿日時: 2006-02-09 21:51
Hongliangさん、返信ありがとうございます。
Declare構文じゃないと、駄目なのかな?と、
こちらでも記述したのですが、同じエラーが発生しました(苦笑)
内部では同じ動きをしているのかな?と、思ってあきらめてしまいましたが…
私の中ではDeclare構文の方が慣れ親しんでいる??ので、こちらで
記述しようかと思います。

さて、本題です。
アンマネージドとの相互運用について説明せよ。と言われると、
全く出来ませんので、下記のアドレスを参考にさせていただきます。
ありがとうございます。これらを理解してから、再度チャレンジしてみます。
「アンマネージ DLL 関数の処理 」
「文字列のマーシャリング」


Tdnr_Symさん、返信ありがとうございます。
DLL側の関数は、提供されている関数なので変更が出来ません。
しかし、この形だと実行効率を落とす形になるのですか…
効率改善要求があった場合に、この関数利用での時間を
測定する必要があるかも知れませんね。


Hongliangさんに、教えていただいたサイトを理解してから
再チャレンジしてみます。出来たら、また結果報告を記述します。
ありがとうございました。
ひさ
会議室デビュー日: 2006/02/09
投稿数: 6
投稿日時: 2006-02-20 14:38
すみません。お知恵をください。

Hongliangさんの紹介して頂いたページを読んで勉強したのですが、
まだ理解していないのかも知れません。よろしくお願いいたします。

Cソース上で引数の変数をポインタ利用している以上、
参照引数として設定するところは理解しました。
なので、今回VB.NET側のDllImportで宣言している関数の引数を
[ByVal]から[ByRef]に変更しました。「値→参照」

戻り値で、構造体の中を固定長配列を利用している部分に関して、
<VBFixedString(8), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=8)> Public A As String
という感じに、定義し直しました。「固定長配列対応」

私の思いでは、Cの関数に参照型の変数で渡し、戻り値を固定長配列に見合った形で
受け取るように変更し、C側の関数が求めるような形に変更したので、
上手く呼び出せるかと思ったのですが、まだ同様の箇所で
「メソッドの型署名は PInvoke と互換していません。」が発生してしまいます。
なにか間違ってる箇所があれば、ご指摘願いたく、よろしくお願いいたします。


[ メッセージ編集済み 編集者: ひさ 編集日時 2006-02-20 14:39 ]
ひさ
会議室デビュー日: 2006/02/09
投稿数: 6
投稿日時: 2006-02-20 18:25
追加です。 今、理解した点がありました。(>、<)

Hongliangさんが、指摘していた戻り値に問題があったのにやっと気づきました。
VB.NET上での宣言のままでは、参照型でCに渡しているっていうことですね。
C側としては値型としてほしいということなので、正しい構造体の修正は、

-----------------------------------------
Structure ACResult
<VBFixedString(8), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=8)>Public A As String
<VBFixedString(9), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=9)>Public B As String
<VBFixedString(21), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=21)>Public C As String
Public Sub New(ByVal A As String, ByVal B As String, ByVal C As String)
Me.A = A
Me.B = B
Me.C = C
End Structure
-----------------------------------------
と、いうことですか。 理解不足でスミマセン。

ですが、この修正を行っても、
「メソッドの型署名は PInvoke と互換していません。」が発生してしまいます。
なにか別の所に誤りがあるのでしょうか。 ご教授願います。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-02-20 19:13
そう言えばあまり聞かない例外だなぁ、と思って追試してみたら……うわぁ。

関数が構造体を返すとき、その構造体に MarshalAs 属性による固定長配列/文字列メンバが含まれていると問答無用で MarshalDerectiveException が投げられるようですね。
// C# 2.0 の fixed キーワードによる固定長配列では問題ないのですけど。
仕様の穴って感じですねぇ。



ん〜、ちょちょいと片づけられるのはどうにも。

例えばこんな関数を利用するとして、
コード:

C : Hoge.h
typedef struct {
char value[4];
} HOGE;
_declspec(dllexport) HOGE GetHoge(char* value);



CやManaged C++で構造体ポインタをパラメータに追加した関数を定義してラップする、とか。
コード:

C : wrapper.c
void GetHogePtr(char* value, HOGE* retval) {
*retval = GetHoge(value);
}
VB.NET :
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure Hoge
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=4)> Public Value As String
End Structure
Public Declare Ansi Sub GetHoge Lib "wrapper.dll" Alias "GetHogePtr" ( _
ByVal value As String, ByRef retval As Hoge)
Public Shared Sub Test()
Dim foo As New Hoge
GetHoge("test", foo)
Console.WriteLine(foo.Value)
End Sub



構造体にbyteフィールドを必要な数だけ敷き詰める、とか。
コード:

VB.NET :
Public Structure Hoge
Public Value1 As Byte
Public Value2 As Byte
Public Value3 As Byte
Public Value4 As Byte
'文字列との相互変換は System.Text.Encoding クラスを使う
End Structure
Public Declare Ansi Function GetHoge Lib "hoge.dll" ( _
ByVal value As String) As Hoge
Public Shared Sub Test()
Dim foo As Hoge = GetHoge("test")
Console.WriteLine("{0:X2} {1:X2} {2:X2} {3:X2}", _
foo.Value1, foo.Value2, foo.Value3, foo.Value4)
End Sub



そのくらいしか解決策が思いつかないな……。


引用:

Cソース上で引数の変数をポインタ利用している以上、
参照引数として設定するところは理解しました。
なので、今回VB.NET側のDllImportで宣言している関数の引数を
[ByVal]から[ByRef]に変更しました。「値→参照」


.NET の参照と、C のポインタは、マーシャラが同じ意味として扱えるようにします。つまり C からポインタを要求されたら、.NET は参照を渡すことになります。
char* はポインタを要求しますよね。ですから参照を渡す必要があります。
さてここで問題は、.NET には「値型」( Structure ) と「参照型」( Class ) の区別があることです。
そして更に問題をややこしくするのは、引数の渡し方として「値渡し」( ByVal ) と「参照渡し」( ByRef ) の二つがあることです。
それぞれの型と渡し方は任意に組み合わせられるので、「値型の参照渡し」「参照型の値渡し」などもできます。
先述したように、参照 = ポインタになりますから、「値型の参照渡し」はポインタを渡すことになります。「参照型の値渡し」もポインタを渡すことになります。
では「参照型の参照渡し」は? 当然、ポインタのポインタを渡すことになりますね。

ところで System.String は値型ですか、参照型ですか?



引用:

コード:

Structure ACResult
<MarshalAs(UnmanagedType.ByValTStr, SizeConst: = 8)>Public A As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst: = 9)>Public B As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst: =21)>Public C As String
Public Sub New(ByVal A As String, ByVal B As String, ByVal C As String)
Me.A = A
Me.B = B
Me.C = C
End Sub
End Structure




まず、VBFixedArray/VBFixedString は、VB の過去との互換性のために残されている一部の関数 ( Len とか FileGet とか) のための属性ですので、アンマネージドとのマーシャリングには関係ありません。
このコンストラクタを使うのはあくまでマネージドのコードのみです。アンマネージドはそもそもコンストラクタを含むメソッドを認識しません(というか、マーシャラは単にフィールドのバイト表現をアンマネージドに渡すだけと考えるのが分かりやすいです。返ってきたときは、そのバイト表現をマーシャラがマネージオブジェクトの各フィールドに配置し直す、と)。
今回の問題とは関係ないですね。


// 最近方向がずれてしまった回答ばかりしてしまってしょんぼり。
// もうちょっと確認してから回答するんだ私。


[ メッセージ編集済み 編集者: Hongliang 編集日時 2006-02-20 19:17 ]
ひさ
会議室デビュー日: 2006/02/09
投稿数: 6
投稿日時: 2006-02-20 19:50
Hongliangさん、レスありがとうございます。
いつも助かります。m(_ _)m


>
>関数が構造体を返すとき、その構造体に MarshalAs 属性による
>固定長配列/文字列メンバが含まれていると問答無用で
>MarshalDerectiveException が投げられるようですね。
>
えっ・・・(>、<) ということは、通常にやっても固定長配列メンバが含まれているので、
今回のケースではMarchalDerectiveExceptionが発生していたということですか(; ;)
うー。でも、回避方法も記載して頂き、ありがとうございます。

>
>CやManaged C++で構造体ポインタをパラメータに追加した関数を
>定義してラップする、とか。
>

ん?! 技術的に難しそうです。ちょっと勉強してみます(汗)


>
>構造体にbyteフィールドを必要な数だけ敷き詰める、とか。
>

こちらの方法は、VBだけの世界でなんとかなりそうなので、
こっちを試してみます。(汗×2)(^^;


>
>ところで System.String は値型ですか、参照型ですか?
>

System.Stringは参照型です。 ということは、私が改めて修正したのは、
「参照型の参照渡し」ポインタのポインタですね(>、<)
[ByVal A As String]←で「参照型の値渡し」ですね。
これでポインタを渡せます。(^^;) 混乱してしまいました。


>
>このコンストラクタを使うのはあくまでマネージドのコードのみです。
>アンマネージドはそもそもコンストラクタを含むメソッドを認識しません
>(というか、マーシャラは単にフィールドのバイト表現をアンマネージドに
>渡すだけと考えるのが分かりやすいです。返ってきたときは、
>そのバイト表現をマーシャラがマネージオブジェクトの各フィールドに
>配置し直す、と)。
>

とんちんかんな構造体を作成してしまったようです(汗)
ということは、まだ、マーシャラの機能をまだ理解していないようです<私
マーシャラはマネージドとアンマネージドの架け橋をしてくれるものと
いう認識が正しいようですね。わざわざ説明して頂き、ありがとうございます。


>// 最近方向がずれてしまった回答ばかりしてしまってしょんぼり。
>// もうちょっと確認してから回答するんだ私。

すみません。私の理解が不足してまして・・・(汗)
さくっと、理解出来るように頑張ります。
とりあえず、試してみます。 ありがとうございました。

引き続き、出来たらまたこちらの方に記載します。
.NETは奥深いです(>。<)

スキルアップ/キャリアアップ(JOB@IT)