皆様こんにちは
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
の使い方に間違いがあるのでしょうか?
よろしくお願い致します。
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 …
参考URL:http://www.microsoft.com/japan/developer/library …
ご返答ありがとうございます。
ネットが使用できない環境にいたため、ご返事が遅れてしまいました。
あれから私もちょうど参考URLで挙げていただいたサイトを見ていたので
御指摘の箇所が怪しいと考え修正した所、今の所は問題が発生していません。
お付き合いありがとうございました。色々勉強になりました。
またよろしくお願い致します。
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()と同じように考えてしまっていました。
間違った情報を記述してしまい、申し訳ないです。
No.7
- 回答日時:
>SafeArrayGetElement()とSafeArrayPutElement()が新たにメモリアロケートするのであれば問題ありませんが、
>そうでないならば、VB側で確保されたメモリ領域をDLL側で解放し、VBに渡したメモリ領域をDLL内で解放してしまうのはランタイムエラーになってしまう可能性があります。
SafeArrayGetElement()で、同じ配列インデックスから2度以上BSTRを取得したときに、別のポインタが返されることから新たに領域確保されていると考えられます。
#参考url中のサンプルソースもしてますし。
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構造体の中身をそのままアクセスしていると思うのですが?
s2tさんとtaka_tetsuさんのやりとりの内容がはっきり申しまして
なかなか理解できませんでしたが、なんとかわかる様になった気がします、
おつきあいありがとうございました。
No.4
- 回答日時:
>パターン(1),(2)を使用すればSysAllocString時に
>CASTをする必要が無いと思うのですが、
>VB側で戻りを確認すると
>各文字間に制御コード?が入ってしまいました。
>パターン(3)だと正常に戻っている様です。
あのう・・変数dataに入っているのがVBから渡された文字列であれば、すでにUNICODEなんですけど。
(1),(2)で失敗して、ただコピーしてるだけの(3)で成功しているのであれば、なおさらです。
失敗の理由は、UNICODE文字列を、さらにUNICODEに変換したためだと思われます。
この回答への補足
お付き合いありがとうございます。
申し訳有りません。言葉足らずだった様です。
>あのう・・変数dataに入っているのがVBから渡された文字列であれば、すでに>UNICODEなんですけど。
記述したコードは新たにテスト用で作成したもので
変数dataはDLL内にて値を代入しています。
lstrcpy(data, "TEST");という様に。
もう少し調べてみます。
No.3
- 回答日時:
ATLを利用している場合はUSES_CONVERSION;を宣言して、OLE2T()やT2OLE()マクロを用いて変換してやると、UNICODEとANSIのどちらにも対応できます。
_bstr_tを利用するのもひとつの手です。
あと、char型ではなく、TCHAR型を使いましょう。
もう一つ気になったのは
SysFreeString (inElement);
SysFreeString (outElement);
は必要無いのでないでしょうか?
この場合のメモリの解放は呼び出し側で行うべきであり、コードの中で解放してしまうとVBランタイム側でエラーになりませんか?
ご回答ありがとうございます。
>ATLを利用している場合はUSES_CONVERSION;を宣言して、OLE2T()やT2OLE()マク>ロを用いて変換してやると、UNICODEとANSIのどちらにも対応できます。
すみませんATLをわかっていなかったため調べしたが、使用していません。
>SysFreeString (inElement);
>SysFreeString (outElement);
>は必要無いのでないでしょうか?
inElement,outElementを使用してSysArrayPutElement,SysArrayGetElementした
ので解放してよいと思っているのですが、問題があるのでしょうか?
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);
この回答への補足
詳細ありがとうございます。
>BSTRはUNICODE文字列なので、ANSI用のAPIで直接扱うことはできません。
>なので、方法としては、
>1.ANSI→UNICODEの変換を行う。
>2.UNICODE用のAPI(lstrcpyWなど)を使う
ということですのでVB戻り部分について
以下の様にテストしてみました。
wchar_twcstr[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)の関数はワイド文字列への変換と
思うのですが、使えないのでしょうか?
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文字列をキャストしてそのまま渡してもだめです。
早速のご回答ありがとうございます。
ご教授いただいた内容で調べてみます。
ただ元のソースでもおちたりおちなかったりで
なかなか判断しずらいでしょうね。
>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が見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- Visual Basic(VBA) VBA横データを縦にしたいです 2 2023/08/08 19:38
- Visual Basic(VBA) 複数シートの複数列に入力されているデータを重複なしで抽出するVBAを作りたいです。 9 2022/06/17 10:33
- Visual Basic(VBA) 別シートのデータを参照して値を入れたい。 まとめデータシートのC列D列の値を商品一覧シートのコードが 7 2022/08/17 13:20
- Excel(エクセル) Excelにて、フォルダ内のTextファイルをマクロで統合すると文字化けしてしまう時の解消コード 4 2023/01/01 07:32
- Visual Basic(VBA) Vbaで数式をポーランド記法に変換するコードを作って実行しようとするとフリーズします。 1 2022/05/24 17:53
- Visual Basic(VBA) VBAでのMATCH関数 3 2022/10/17 19:06
- Excel(エクセル) エクセルVBA、ファイル名をセルの値で保存の方法を教えてください。 おそれいります。こちらで数々のエ 6 2023/06/30 22:17
- Visual Basic(VBA) 複数csvを横に追加していくマクロについて 2 2023/04/25 09:19
- その他(Microsoft Office) マクロVBAについて 1 2022/09/06 18:12
- PHP 配列の値の更新方法について 1 2022/08/05 09:49
このQ&Aを見た人はこんなQ&Aも見ています
-
新NISA制度は今までと何が変わる?非課税枠の拡大や投資対象の変更などを解説!
少額から投資を行う人のための非課税制度であるNISAが、2024年に改正される。おすすめの銘柄や投資額の目安について教えてもらった。
-
DLL<->VB間での受け渡し(文字列・ポインタ)
Visual Basic(VBA)
-
Cで作成したDLL関数をVBから呼び 引数渡し方法
Visual Basic(VBA)
-
C#で作成したDLLをExcelVBAで利用する
その他(プログラミング・Web制作)
-
-
4
std::stringからLPCWSTR型への変換
C言語・C++・C#
-
5
2次元配列を戻り値とする関数?
C言語・C++・C#
-
6
String だと「 ByRef引数の型が一致しません 」というエラーが出ます。なぜ?
Visual Basic(VBA)
-
7
#include <Windows.h>というヘッダファイルについて
C言語・C++・C#
-
8
C++で作成したDLLにVBAから配列渡し
C言語・C++・C#
-
9
C#でstringをポインタとして渡す
C言語・C++・C#
-
10
IT初心者です 仕事で、vb.netで作成されたdllをvbaで呼び出すプログラムを作成しろと言われ
その他(プログラミング・Web制作)
関連するカテゴリからQ&Aを探す
おすすめ情報
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
%dなどの違い
-
C++で入力した文字列から数字を...
-
プログラムによく出てくるst...
-
nullと""、\\0とEOFの違いにつ...
-
C++で文字列の右端から特定の文...
-
C言語の課題で困っています;
-
16進数を2文字ずつ配列に格納し...
-
VB6.0でのバイナリデータの扱い...
-
VBA-DLLの引数受け渡しについて
-
バイナリファイル中の日本語文...
-
C#でstringをポインタとして渡す
-
数字の入った配列をファイルへ...
-
文字コードによる表記
-
アルファベットの出て来た回数
-
CSVファイルの「”」について
-
シリアル通信で0x00を送信した...
-
c言語によって文字列を逆順する...
-
TCL言語で文字列検索方法を教え...
-
C言語のコンパイル時に表示され...
-
セグメントエラー
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
C#でstringをポインタとして渡す
-
プログラムによく出てくるst...
-
C++で入力した文字列から数字を...
-
nullと""、\\0とEOFの違いにつ...
-
c#で他のアプリの文字入力フォ...
-
TCL言語で文字列検索方法を教え...
-
16進数を2文字ずつ配列に格納し...
-
シリアル通信で0x00を送信した...
-
_tcscpy_s(wcscpy_s)の第二引数...
-
WSH(VBS)でJSONの文字列を読み...
-
VB6.0でのバイナリデータの扱い...
-
C++で文字列の右端から特定の文...
-
数字の入った配列をファイルへ...
-
%dなどの違い
-
構造体→文字列→構造体 をする方法
-
char型配列の最大要素数
-
VBA-DLLの引数受け渡しについて
-
[C++]WCHARの1文字目しか表示で...
-
Shift_JIS(16進)を文字に変換す...
-
CSVファイルの「”」について
おすすめ情報