アプリ版:「スタンプのみでお礼する」機能のリリースについて

VCで作成されたDLLの参照型Stringの読込みに困っております。
ご存じの方、お教え下さい。

VB6では次のような定義で、問題なく実行できます。
Private Declare Function VcToVb _
Lib "xxxxxxx.dll" Alias "VcToVb" _
(ByVal Input_DATA As String, ByVal Input_DATA_Cnt As Long, _
ByRef strData As String, ByRef Err_Cnt As Long, ByRef Err_DATA As String) As Long
------------------------------------
Out_Str , Err_Str の領域を確保
iRent = Check_VcToVb1(Input_STR, Input_Cnt, Out_STR, Err_Cnt, Err_STR)

これを VB.NETで実施
Private Declare Ansi Function VcToVb _
 Lib "xxxxxxx.dll" Alias "VcToVb" _
(ByVal Input_DATA As String, ByVal Input_DATA_Cnt As Int32, _
<MarshalAs(UnmanagedType.LPStr)> ByRef strData As StringBuilder, _
ByRef err_cnt As Int32, ByRef Err_DATA As StringBuilder) As Int32
(テストのため、strDataのみ)
--------------------------------------
Out_Str , Err_Str の領域を確保

iRent = Check_VcToVb1(Input_STR, Input_Cnt, Out_STR, Err_Cnt, Err_STR)
*******************************************
調べた結果、VB.NETでは参照型は簡単にはいかないみたいで、StringBuilderを使用するとか、色々な方法を試しました。

結果は戻ってくるのですが、問題は DLLが文字列を返すときに、文字列内の項目区切文字として
chr(0)を設定します。
このため、結果は Chr(0)の前までしか設定されません。
(VB.NETが Chr(0)を文字列の最後と判断する?)

これを解決する方法はありますでしょうか。

現在は VB6で呼出す DLLを作成し、VB.NETはこれを使用していますが、
C#.Netならできるのでしょうか。

よろしくお願い申し上げます。

A 回答 (6件)

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は適宜修正してくださいね
    • good
    • 0
この回答へのお礼

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 で切られる?

解決策があれば、よろしくお願い申し上げます。

お礼日時:2011/12/09 09:11

すみません 一つ間違いが



char(0)をvbTabに置換
for n as integer = 0 to bb.length
  if bb(n) = 0 then bb(n) = 9
next

vbTabでは代入できませんので 9を代入してください
    • good
    • 0

第3引数が問題なのでしょうか


もしかしたら第5引数の ByRefのStringBuilderかも知れないですよ

当方も ByRefのStringBuilderを与えたところ C側が char**(またはwtchar_t**)でないと同じようなエラーが発生してました

このDLL関数の戻り値は何を示しているのでしょう 返したバイト数なのでしょうか

違うなら配列の長さに対して 0をTABなどに置き換えてから GetStringを使いましょう
    • good
    • 0
この回答へのお礼

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 でも同一現象です。

何か、ヒントでもあればよろしくお願い申し上げます。

お礼日時:2011/12/10 11:39

VC側のヘッダーファイルや インポートライブラリは有るのでしょうか



どうも .NETのマーシャリング中に char(0)以降を捨ててしまうように思えるんですけど

VC側の受けが BSTR* なっているのであれば 途中にラッパーをかましてやらないといけないようです
    • good
    • 0
この回答へのお礼

redfox63さん 回答、ありがとうございます。

この DLLは 2005年3月に作成されており、仕様書には DelphiとVB6での呼出しサンプルしかありません。
VB6で問題なく動作するので VB6でラッパーを作り、これを VB.NETで呼んでいました。
しかし、 Windows7対応等でVB6からの脱却のため、何とか VB.NETで呼べないか、再挑戦をしました。
本DLLは開発者とは連絡がとれず、カット&トライで挑戦している所です。

そろそろ、あきらめてラッパーを作ろうと思いますが、C#.NETなら大丈夫なのでしょうか?

お礼日時:2011/12/12 10:38

>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

に置き換えるとどうなりますか?
    • good
    • 0
この回答へのお礼

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バイトとなってしまいます。

もう少し、試験してみます。

お礼日時:2011/12/12 10:53

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)
といった具合で 分割取得できるようです
    • good
    • 0
この回答へのお礼

redfox63さん 回答、ありがとうございます。

これから Visual C++ を勉強します。
どこかに クラスライブラリ のサンプルがあるといいのですが……

お礼日時:2011/12/13 15:02

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!