- ベストアンサー
VB.NET DLL参照型String読込みに困っています
- VB6で問題なく実行できるDLLの参照型Stringの読込みにおいて、VB.NETでは文字列内の項目区切文字の設定に問題が発生しています。
- VB.NETでは参照型の扱いが簡単ではないため、StringBuilderを使用するなどの方法を試しましたが、文字列内の項目区切文字(chr(0))の前までしか取得できません。
- 現在はVB6で呼び出せるDLLを作成し、VB.NETで使用していますが、C#.NETなら問題を解決できる可能性があります。
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
C#でラッパーを書いても同じだと思います ネイティブのC++でラッパーを書いてやる方がいいように思いますよ C++の文字セットを『マルチバイト文字セットで使う』に変更 typedef int (CALLBACK* LPFNVC2VB)(BSTR, int, BSTR*, int* BSTR*); extern "C" int __declspec(dllexport) mywrpper( BSTR sIn, int nIn, LPSTR psOut, int* pnError, BSTR* psError ) { ____ HINSTANCE hDLL; ____ LPFNVC2VB lpFnFunc = NULL; ____ int nRet = -1; ____ // ライブラリーを取得 ____ if ( NULL != ( hDLL = LoadLibrary("xxxx.dll") ) { ____ ____ // 関数ポインタを取得 ____ ____ if ( lpFnFunc = (LPFNVC2VB)GetProcAddress( hDLL, "VcToVb" ) ) { ____ ____ ____ // 元の関数に引数を準備 ____ ____ ____ int nLen = ::SysStringByteLen( (BSTR)psOut ); ____ ____ ____ BSTR pBStr = ::SysAllocStringByteLen( psOut, nLen ); ____ ____ ____ nRet = (lpFnFunc)(sIn, nIn, &pBstr, pnErr, psErr ); ____ ____ ____ // 返ってきたデータを呼び出し元に返却する準備 ____ ____ ____ for( int n=0; n < nLen; n++ ) { ____ ____ ____ ____ psOut[n] = ((LPSTR)pBstr)[n]; ____ ____ ____ } ____ ____ ____ // ローカル変数の後始末 ____ ____ ____ ::SysFreeString( pBstr ); ____ ____ } ____ ____ // ライブラリーの後始末 ____ ____ FreeLibrary( hDLL ); ____ } } といったラッパーDLLを準備して Private Declare Ansi Function MyWrpper Lib "MyWrpper.dll" _ ____ ( ByVal sIn as String, ByVal nIn as integer, _ ____ ____ ByVal sOut as String, _ ____ ____ ByRef nErr as Integer, ByRef sError as string ) as integer といった具合の宣言で呼び出して見ましょう 第3引数は ByValにしてください 戻ってきたら dim sa() as String sa = sOut.Split( new String(){ vbNullChar }, StringSplitOptions.RemoveEmptyEntries) といった具合で 分割取得できるようです
その他の回答 (5)
- tsukasa-12r
- ベストアンサー率65% (358/549)
>vb.netで受取ると Hex 31 となり、呼出前の領域サイズ(文字長)と結果の文字長が異なって >います。 → aaa.length は 1となる >これは、Sting , Byte() ,StringBuilder でも同一現象です。 本当にByte() でも aaa.length は 1 になりますか? extern "C" __declspec(dllexport) int VcToVb( LPTSTR pInputData, int inputDataCount, LPTSTR pOutputData, int *pErrorCount, LPTSTR pErrorData ) { LPTSTR p = pOutputData; _tcscpy( p, _T("ABCDEFG") ); p += _tcslen( _T("ABCDEFG") ) + 1; _tcscpy( p, _T("HIJKLMN") ); p += _tcslen( _T("HIJKLMN") ) + 1; _tcscpy( p, _T("OPQRSTU") ); p += _tcslen( _T("OPQRSTU") ) + 1; *pErrorCount = 1; _tcscpy( pErrorData, _T("Error") ); return 1; } という DLL を作成して Private Declare Ansi Function VcToVb _ Lib "DllTest.dll" Alias "VcToVb" ( _ <MarshalAs(UnmanagedType.LPStr)> ByVal Input_DATA As String, _ ByVal Input_DATA_Cnt As Int32, _ <MarshalAs(UnmanagedType.LPArray)> ByVal strData() As Byte, _ ByRef err_cnt As Int32, _ <MarshalAs(UnmanagedType.LPStr)> ByVal Err_DATA As StringBuilder) _ As Int32 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim input_STR As String = "Input String" Dim input_Cnt = 1 Dim Out_STR() As Byte = New Byte(1000) {} Dim Err_Cnt As Integer = 0 Dim Err_STR As StringBuilder = New StringBuilder(1000) Debug.WriteLine(Out_STR.Length) Dim iRent As Integer = VcToVb(input_STR, input_Cnt, Out_STR, Err_Cnt, Err_STR) Debug.WriteLine(Out_STR.Length) で試してみましたけど、VcToVb実行前後とも 1001 と出力されました。 Declare による宣言を DllImport による方法、 例えば、 <DllImport("DllTest.dll")> _ Private Shared Function VcToVb( _ <MarshalAs(UnmanagedType.LPStr)> ByVal Input_DATA As String, _ ByVal Input_DATA_Cnt As Int32, _ <MarshalAs(UnmanagedType.LPArray)> ByVal strData() As Byte, _ ByRef err_cnt As Int32, _ <MarshalAs(UnmanagedType.LPStr)> ByVal Err_DATA As StringBuilder) _ As Int32 End Function に置き換えるとどうなりますか?
お礼
tsukasa-12rさん 回答、ありがとうございます。 > 本当にByte() でも aaa.length は 1 になりますか? はい。 Dim Out_StrB() As Byte = New Byte(55500) {} Dim LngA, LngB As Integer LngA = Out_StrB.Length iRent = Check_VcToVb(Input_STR, Input_Cnt, Out_StrB, Err_Cnt, GetStrC) LngB = Out_StrB.Length MsgBox("LngA:" & LngA & " LngB:" & LngB) ---> LngA:55501 LngB:1 となります。 >DllImport("DllTest.dll")> _ >Private Shared Function VcToVb( _ > …… > に置き換えるとどうなりますか? これは、 保護されているメモリに読み取りまたは書き込み操作を行おうとしました。 他のメモリが壊れていることが考えられます。 となります。 DllImportを使用して、色々と呼出方法変えて、正常終了させても、 OutPutは 1バイトとなってしまいます。 もう少し、試験してみます。
- redfox63
- ベストアンサー率71% (1325/1856)
VC側のヘッダーファイルや インポートライブラリは有るのでしょうか どうも .NETのマーシャリング中に char(0)以降を捨ててしまうように思えるんですけど VC側の受けが BSTR* なっているのであれば 途中にラッパーをかましてやらないといけないようです
お礼
redfox63さん 回答、ありがとうございます。 この DLLは 2005年3月に作成されており、仕様書には DelphiとVB6での呼出しサンプルしかありません。 VB6で問題なく動作するので VB6でラッパーを作り、これを VB.NETで呼んでいました。 しかし、 Windows7対応等でVB6からの脱却のため、何とか VB.NETで呼べないか、再挑戦をしました。 本DLLは開発者とは連絡がとれず、カット&トライで挑戦している所です。 そろそろ、あきらめてラッパーを作ろうと思いますが、C#.NETなら大丈夫なのでしょうか?
- redfox63
- ベストアンサー率71% (1325/1856)
第3引数が問題なのでしょうか もしかしたら第5引数の ByRefのStringBuilderかも知れないですよ 当方も ByRefのStringBuilderを与えたところ C側が char**(またはwtchar_t**)でないと同じようなエラーが発生してました このDLL関数の戻り値は何を示しているのでしょう 返したバイト数なのでしょうか 違うなら配列の長さに対して 0をTABなどに置き換えてから GetStringを使いましょう
お礼
redfox63さん ありがどうございます。 第3引数は この dllの仕様書には Output , 出力パラメータへのポインタ と記載されています。 (文字列はポインタと表現されています、input,output共に) VB6では、ByRef aaa as String で、問題なく呼べます。 ----------------------------------------------------------- 引数が ByRefの文字列で問題が発生しています。 単純な呼出し ByRef aaa As StringBuilder ByRef aaa As String ByRef aaa As Byte() 等で、呼出前に領域を確保して、呼出すと呼出しは正常終了します。 コール先(VC dll)は、この文字列に Che(0)を項目区切れ目して、挿入してきます。 Hex 31 00 30 31 30 31 00 00 20 20 20 20 20 20 …… vb.netで受取ると Hex 31 となり、呼出前の領域サイズ(文字長)と結果の文字長が異なって います。 → aaa.length は 1となる これは、Sting , Byte() ,StringBuilder でも同一現象です。 何か、ヒントでもあればよろしくお願い申し上げます。
- redfox63
- ベストアンサー率71% (1325/1856)
すみません 一つ間違いが char(0)をvbTabに置換 for n as integer = 0 to bb.length if bb(n) = 0 then bb(n) = 9 next vbTabでは代入できませんので 9を代入してください
- redfox63
- ベストアンサー率71% (1325/1856)
Byte配列で受け渡しをやったほうがいいのかも <MarshalAs(UnmanagedType.LPArray,SizeConst:=80)>ByVal aOut() as Byte といった具合に引数を宣言して … SizeConstは適宜修正してください 呼び出し前に領域を確保 dim bb() as Byte redim bb(79) iRent = Check_VcToVb1(Input_STR, Input_Cnt, bb, Err_Cnt, Err_STR) ' char(0)をvbTabに置換 for n as integer = 0 to bb.length if bb(n) = 0 then bb(n) = vbTab next dim ss as String = System.Text.Endocing.Default.GetString(bb) dim s() as String = ss.split( new String(){vbTab}, StringSplitOptions.RemoveEmptyEntries) といった具合で s()に文字列の配列として受け取れますよ Encoding.Defaultは適宜修正してくださいね
お礼
redfox63さん ありがとうございます。 バイト配列で試験しました。しかし、次のエラーとなりました。(呼出時) System.AccessViolationException はハンドルされませんでした。 保護されているメモリに読み取りまたは書き込み操作を行おうとしました。 他のメモリが壊れていることが考えられます。 バイト配列の数をいくつか(少なめ、ジャスト、多め)変更して試験しましたが、同一エラーでした。 次の試験をしてみました。 <MarshalAs(UnmanagedType.LPArray)> ByRef strData() As Byte とすると、呼出しは成功しますが、呼出前のサイズ(55548)が 1となって返ります。 (ByVal だと「保護されているメモリに読み取りまたは書き込み操作を行おうとしました」です) 結果 "1" Hex 31 期待値は次のとおり (VB6での呼出) Hex 31 00 30 31 30 31 00 00 20 20 20 20 20 20 …… バイト配列でも、最初の 00 で切られる? 解決策があれば、よろしくお願い申し上げます。
お礼
redfox63さん 回答、ありがとうございます。 これから Visual C++ を勉強します。 どこかに クラスライブラリ のサンプルがあるといいのですが……