• ベストアンサー

VBA-DLLの引数受け渡しについて

皆様こんにちは VCでDLLを作成し、VBAからString配列を表データでわたし、DLLにて抽出後 VBAに戻す処理を作成しています。 VCよりデバッグ実行すると発生しないのですが VBAから実行するとたまにですが落ちてしまいます。 又、偶然かもしれませんがDLLをリリースモードにしたときしか落ちません。 以下の様なコードを作成し実行しているのですが、 問題があれば御指摘いただければありがたいです。 VCはVer6です。 void WINAPI foo (LPSAFEARRAY FAR* inData, //VBAよりわたされるString配列 LPSAFEARRAY FAR* outData) //VBAへ返すString配列 { BSTR inElment; BSTR outElment; long inIdx[2]; long outIdx[2]; char data[256]; for(行数分ループ) { for(列数分ループ) { //データ獲得処理   //inIdx[0],inIdx[1]にはinData配列へのインデックスが入ります。 SafeArrayGetElement(inData, inIdx, &inElement); lstrcpy(data,(LPCTSTR)inElement); SysFreeString (inElement);   ; //データを返す処理   //outIdx[0],outIdx[1]にはoutData配列へのインデックスが入ります。 element2 = SysAllocString((BSTR)data); SafeArrayPutElement(*outData, outIdx, outElement); SysFreeString (outElement); } } } 尚VBA側は 宣言を Public declare ・・・・ foo(ByRef indata() as string, ByRef outData() as string) としています。 各引数はRedimにて確保しています。 LPSAFEARRAY型変数や それに関係する関数 SysAllocStaring,SafeArrayGetElement,SafeArrayPutElement,SysFreeString の使い方に間違いがあるのでしょうか? よろしくお願い致します。

質問者が選んだベストアンサー

  • ベストアンサー
  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.5

ちゃんと調べずに解答していたので、いくつか間違いがありました。 失礼いたしました。 まず、VBから受け渡しされる文字列(String型)は、DeclareステートメントでDLLの関数を宣言したとき、VBによりUNICODEからANSIに変換され、VBに返すときはANSIからUNICODEに変換されてきたものでした。 このため、hiroshi2001さんのlstrcpyでの処理は正しく、#4での私の解答は間違っていました。 DLLの中でUNICODE⇔ANSIの変換は必要ありませんでした。 では、どこに間違いがあるのかといいますと、 element2 = SysAllocString((BSTR)data); になります。 dataに入っているのはANSIであるため、SysAllocStringで確保される領域サイズとデータ長に矛盾が生じ、メモリ破壊を起こす可能性があります。 なので、SysAllocStringByteLen()を使用する必要があります。 element2 = SysAllocStringByteLen((BSTR)data, lstrlen(data)); これで、アプリケーションエラーは解消すると思われます。 ちなみに、 SysFreeString (inElement); SysFreeString (outElement); は必要ですので残しておいてくださいね。 なお、こちらを参考にしましたのでご覧ください。 http://www.microsoft.com/japan/developer/library/jptech/TechArt/VTools/vb/vb5/dll.htm

参考URL:
http://www.microsoft.com/japan/developer/library/jptech/TechArt/VTools/vb/vb5/dll.htm
hiroshi2001
質問者

お礼

ご返答ありがとうございます。 ネットが使用できない環境にいたため、ご返事が遅れてしまいました。 あれから私もちょうど参考URLで挙げていただいたサイトを見ていたので 御指摘の箇所が怪しいと考え修正した所、今の所は問題が発生していません。 お付き合いありがとうございました。色々勉強になりました。 またよろしくお願い致します。

その他の回答 (7)

  • s2t
  • ベストアンサー率79% (47/59)
回答No.8

MSDNでSafeArrayGetElement()とSafeArrayPutElement() の説明を読んでみると確かにメモリを割り当てているようですね。 SafeArrayGetElement() > If the data element is a string, object, or variant, > the function copies the element in the correct way. SafeArrayPutElement() > If the data element is a string, object, or variant, > the function copies it correctly when the safearray is destroyed. > If the existing element is a string, object, or variant, it is cleared correctly. なので、 SysFreeString (inElement); SysFreeString (outElement); は、必要ですね。 SafeArrayAccessData()、SafeArrayUnaccessData()と同じように考えてしまっていました。 間違った情報を記述してしまい、申し訳ないです。

  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.7

>SafeArrayGetElement()とSafeArrayPutElement()が新たにメモリアロケートするのであれば問題ありませんが、 >そうでないならば、VB側で確保されたメモリ領域をDLL側で解放し、VBに渡したメモリ領域をDLL内で解放してしまうのはランタイムエラーになってしまう可能性があります。 SafeArrayGetElement()で、同じ配列インデックスから2度以上BSTRを取得したときに、別のポインタが返されることから新たに領域確保されていると考えられます。 #参考url中のサンプルソースもしてますし。

  • s2t
  • ベストアンサー率79% (47/59)
回答No.6

comutil.hをインクルードすれば_bstr_tが利用できます。 DLLをWinNT系のOS以外で利用しないのであればlstrcpyW()を用いても問題ありませんが、汎用性を考えるならば_bstr_tなどを利用してTCHAR型にキャストし、lstrcpy()を用いた方がいいと思います。 > ちなみに、 > SysFreeString (inElement); > SysFreeString (outElement); > は必要ですので残しておいてくださいね。 これは本当ですか? SysAllocString()で確保してVB側に渡した場合、確保した領域はVB側で解放されると認識しています。 SafeArrayGetElement()とSafeArrayPutElement()が新たにメモリアロケートするのであれば問題ありませんが、そうでないならば、VB側で確保されたメモリ領域をDLL側で解放し、VBに渡したメモリ領域をDLL内で解放してしまうのはランタイムエラーになってしまう可能性があります。 SafeArrayGetElement()はSafeArrayLock()とSafeArrayUnlock()を自動化しているものだったと思うので、SAFEARRAY構造体の中身をそのままアクセスしていると思うのですが?

hiroshi2001
質問者

お礼

s2tさんとtaka_tetsuさんのやりとりの内容がはっきり申しまして なかなか理解できませんでしたが、なんとかわかる様になった気がします、 おつきあいありがとうございました。

  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.4

>パターン(1),(2)を使用すればSysAllocString時に >CASTをする必要が無いと思うのですが、 >VB側で戻りを確認すると >各文字間に制御コード?が入ってしまいました。 >パターン(3)だと正常に戻っている様です。 あのう・・変数dataに入っているのがVBから渡された文字列であれば、すでにUNICODEなんですけど。 (1),(2)で失敗して、ただコピーしてるだけの(3)で成功しているのであれば、なおさらです。 失敗の理由は、UNICODE文字列を、さらにUNICODEに変換したためだと思われます。

hiroshi2001
質問者

補足

お付き合いありがとうございます。 申し訳有りません。言葉足らずだった様です。 >あのう・・変数dataに入っているのがVBから渡された文字列であれば、すでに>UNICODEなんですけど。 記述したコードは新たにテスト用で作成したもので 変数dataはDLL内にて値を代入しています。 lstrcpy(data, "TEST");という様に。 もう少し調べてみます。

  • s2t
  • ベストアンサー率79% (47/59)
回答No.3

ATLを利用している場合はUSES_CONVERSION;を宣言して、OLE2T()やT2OLE()マクロを用いて変換してやると、UNICODEとANSIのどちらにも対応できます。 _bstr_tを利用するのもひとつの手です。 あと、char型ではなく、TCHAR型を使いましょう。 もう一つ気になったのは SysFreeString (inElement); SysFreeString (outElement); は必要無いのでないでしょうか? この場合のメモリの解放は呼び出し側で行うべきであり、コードの中で解放してしまうとVBランタイム側でエラーになりませんか?

hiroshi2001
質問者

お礼

ご回答ありがとうございます。 >ATLを利用している場合はUSES_CONVERSION;を宣言して、OLE2T()やT2OLE()マク>ロを用いて変換してやると、UNICODEとANSIのどちらにも対応できます。 すみませんATLをわかっていなかったため調べしたが、使用していません。 >SysFreeString (inElement); >SysFreeString (outElement); >は必要無いのでないでしょうか? inElement,outElementを使用してSysArrayPutElement,SysArrayGetElementした ので解放してよいと思っているのですが、問題があるのでしょうか?

  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.2

>>lstrcpyAでUNICODE文字列をcharへコピーする >lstrcpyAというとAはANSIのAでしょうか? >MSDNにはlstrcpyしか乗っていません。 >UNICODEをANSIにコピーするという関数なのでしょうか? ご想像のとおり、ANSIのAです。 ただし、UNICODEをANSIにコピーする関数ではありません。 あと、lstrcpyWというワイド文字列用のAPIも存在します。 つまり、lstrcpyはANSI文字列の操作、lstrcpWはワイド文字列(UNICODE)の操作を行えます。 この2つのAPIは、lstrcpyと記述したとき、 #define UNICODEがなければlstrcpyA、lstrcpyWに再定義されます。なので、MSDNにはlstrcpyしか記述がありません。 BSTRはUNICODE文字列なので、ANSI用のAPIで直接扱うことはできません。 なので、方法としては、 1.ANSI→UNICODEの変換を行う。 2.UNICODE用のAPI(lstrcpyWなど)を使う のどちらかで処理を行う必要があります。 なお最後にWが付くワイド文字用のAPIは、NT系のOSのみ実装されているので、9x系のOSでは使用できないので注意してください。 また、2.の方法で行うのであれば、対比した内容の格納先はcharではなく、wchar_tになります。 >Helpを確認した所、SysAllocStringの引数はOLECHAR FAR*となっていました。 >又 BSTRはtypedef OLECHAR __RPC_FAR *BSTRとなっていますので >問題ないと思ったのですが何かからくりがあるのでしょうか? char[]を使用し、無理やりキャストしているところが問題です。 wchar_t[]であれば、キャストの必要はありません。 >サンプル等はSysAllocString(OLESTR("Hello"));となっているのですが >SysAllocString(OLESTR(変数名))が出来ず困っています。 SysAllocString(OLESTR("Hello")); は、 SysAllocString(L"Hello")); と展開されます。 つまり、文字定数をUNICODE文字列で定義していることになります。 このように展開されるので、OLESTR()は変数は使えません。 なので、変数内の文字列をSysAllocString()に渡すには、あらかじめUNICODEにした文字列を渡す必要があります。 wchar_t w[] = L"Hello"; SysAllocString(w);

hiroshi2001
質問者

補足

詳細ありがとうございます。 >BSTRはUNICODE文字列なので、ANSI用のAPIで直接扱うことはできません。 >なので、方法としては、 >1.ANSI→UNICODEの変換を行う。 >2.UNICODE用のAPI(lstrcpyWなど)を使う ということですのでVB戻り部分について 以下の様にテストしてみました。 wchar_t wcstr[256]; パターン(1) mbstowcs(wcstr, data, lstrlen(data)); パターン(2) MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, data, lstrlen(data), wcstr, 256); パターン(3) lstrcpyW(wcstr, data); outElement = SysAllocString(wcstr); SafeArrayPutElement(*outData, outIdx, outElement); SysFreeString (outElement); パターン(1),(2)を使用すればSysAllocString時に CASTをする必要が無いと思うのですが、 VB側で戻りを確認すると 各文字間に制御コード?が入ってしまいました。 パターン(3)だと正常に戻っている様です。 (1),(2)の関数はワイド文字列への変換と 思うのですが、使えないのでしょうか?

  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.1

その1 >BSTR inElment; >char data[256]; >SafeArrayGetElement(inData, inIdx, &inElement); >lstrcpy(data,(LPCTSTR)inElement); BSTRは32bitではUNICODE文字、charはANSI文字ですけど その辺は理解されていますか? おそらく#define UNICODEは定義されていないと思われるので、lstrcpyAでUNICODE文字列をcharへコピーすることになります。 その2 >element2 = SysAllocString((BSTR)data); ここも同様です。 SysAllocStringはOLESTR型の文字列を引数で受け取るはずでは? OLESTRは32bitではUNCODEです。ANSI文字列をキャストしてそのまま渡してもだめです。

hiroshi2001
質問者

お礼

早速のご回答ありがとうございます。 ご教授いただいた内容で調べてみます。 ただ元のソースでもおちたりおちなかったりで なかなか判断しずらいでしょうね。 >lstrcpyAでUNICODE文字列をcharへコピーする lstrcpyAというとAはANSIのAでしょうか? MSDNにはlstrcpyしか乗っていません。 UNICODEをANSIにコピーするという関数なのでしょうか? >BSTRは32bitではUNICODE文字、charはANSI文字ですけど >その辺は理解されていますか? 色々見ているのですが、なかなか理解できていません。 お恥ずかしい >SysAllocStringはOLESTR型の文字列を引数で受け取るはずでは? >OLESTRは32bitではUNCODEです。ANSI文字列をキャストしてそのまま渡してもだめです Helpを確認した所、SysAllocStringの引数はOLECHAR FAR*となっていました。 又 BSTRはtypedef OLECHAR __RPC_FAR *BSTRとなっていますので 問題ないと思ったのですが何かからくりがあるのでしょうか? サンプル等はSysAllocString(OLESTR("Hello"));となっているのですが SysAllocString(OLESTR(変数名))が出来ず困っています。 再質問になって申し訳ないですが、 よろしければお願いいたします。

関連するQ&A

専門家に質問してみよう