Visual C++6.0で作成したDLLをVisualbasic6.0で使用しています.
ファイル名を渡すとそのファイルの中にあるデータの数を出すというDLLを作成しました.
このDLLはVisual C++では正常に使用できたので正しいと思います.
このDLLをVisual Basicで使用すると,うまくいきません.
DLLの中でもしファイルをopenできなかったらreturnを1で返すようにしているのですが,ファイル名を渡すとreturnが1となってしまいます.
これはどうしてでしょうか.
これだけの情報では、答えようが無いと思いますけど。(^^;
> このDLLはVisual C++では正常に使用できたので正しいと思います.
> このDLLをVisual Basicで使用すると,うまくいきません.
可能性1) C++的には正しいのだが、VB向けの設計にはなっていなかった。
可能性2) DLL自体は正しいが、Declare 宣言に問題があった。
可能性3) DLL自体は正しいが、VBからの呼び出し処理が適切ではなかった。
可能性4) DLL自体に問題があり、C++で正常動作しているようにみえたのは偶然。
> ファイル名を渡すとreturnが1となってしまいます.
実際のコード等を見ないと何とも言えませんが、思いつくところでは、
・フルパス/相対パスの指定が考慮されていなかった。
・C++からの場合と、VBからの場合とで、カレントディレクトリが異なっていた。
・引数指定に問題があり、DLL側で適切なファイル名文字列が渡っていなかった。
とか……。
VBからの呼び出し処理が間違っていました.
ファイルの渡しのうまくいき,起動するのですが,そのあとに
「このプログラムは不正な処理を行ったので強制終了されます.」
と出て,終了してしまいます.
これはどうしてでしょうか.
Basicのコードは
Private Sub Command1_Click()
Dim ret, n, data() As Double
ret = openfile("abc.txt", n, data)
MsgBox n
End Sub
標準モジュールは
Declare Function openfile Lib "test1.dll" Alias "_openfile@12" (ByVal filename As String, ByRef n As Long, ByRef data() As Double) As Long
C++のDLLのソースは
#include "stdafx.h" // for Visual C コンソール アプリケーション用のエントリ ポイントの定義
#include <stdio.h>
#include "test1.h"
EXTERN long __stdcall openfile(char *filename,long &n,double data[]){
FILE *fr;
if ((fr = fopen(filename,"r")) == NULL){
printf("リストファイル%sが見つからない!\n",filename);
return -1;
}
n = 0;
while (fscanf(fr,"%d\n",&data[n] )!= EOF){
++n;
}
return 0;
}
ヘッダは
#define EXTERN extern "C" __declspec(dllexport)
EXTERN long __stdcall openfile(char *,long &,double []);
としています.
どこか処理がおかしいところがあるでしょうか.
よろしくおねがいします.
とりあえず、
>while (fscanf(fr,"%d\n",&data[n] )!= EOF){
double型の値を読み込むのであれば %d ではなく、 %lf にしてください。
で、VB側はポインタでやり取りするのであれば、先頭要素を渡すようにしてください。
(配列で割り取りするならば、VC側をSAFEARRAYにしなければだめ)
よって、
>ret = openfile("abc.txt", n, data)
ret = openfile("abc.txt", n, data(0))
>Declare Function openfile Lib "test1.dll" Alias "_openfile@12" (ByVal filename As String, ByRef n As Long, ByRef data() As Double) As Long
Declare Function openfile Lib "test1.dll" Alias "_openfile@12" (ByVal filename As String, ByRef n As Long, ByRef data As Double) As Long
それと、一応fcloseは書いておいたほうがよさそう。
誤字
>(配列で割り取りするならば
(配列でやり取りするならば
# なぜ「や」が「わ」になったんだろ、、、orz
あ〜よく見たらだめやんけ。
> Dim ret, n, data() As Double
VB側で配列を用意できない場合、VC側でmallocやらで領域を設定しないとだめかなぁ、、、
(保障ナシ。配列型ではなく確保したポインタを指すようにするならば出来そうだけど)
SAFEARRAYならCreateSafeArrayあたりで配列を作成できるけど。
たしかに「インデックスが有効範囲にありません」というエラーが発生しました.
mallocもうまくいきませんでした.
そこでSAFEARRAYというのを使おうと思ったのですが,調べてもよく分かりません.
longやdoubleの変わりにSAFEARRAYを使うのでしょうか.
上のプログラムでいうとどのような処理をするのでしょうか.
簡単な例などありましたら,おしえてください.
よろしくお願いします.
とりあえず、ある程度VB側で配列を用意するならば
最初の指摘のとおりの変更で動くでしょう。
(そういう場合はC側に配列数の上限を渡すべき。ポインタでは終端がわからないので
バッファオーバラン(メモリを確保していない領域を参照して落ちる)が起こる可能性があります。)
SAFEARRAYを使うとなると、SafeArrayCreateという関数を使って
配列を作ります。
ただ、C側で配列数が固定でないので、毎回ReDimとなるとコストが高いでしょう。
ですので、C側でファイルをすべて読み込む処理を作りこんでから
配列を作るようなつくりにすべきでしょう。
まずは、SAFEARRAYをつかうのではなく、C言語の動的配列の使い方を
調べてください。
(mallocやreallocあたりを使う。MFCならCArrayとかでいけるかな。)
ちなみにSafeArrayCreateで過去ログ検索やGoogle検索すればサンプルはいくらでも見つかると思います。
ということで、この質問はVBの質問ではなくなりますね。
ちなみに、なんでこんな単純な作業をDLLにするのでしょうか?
いままで書いてきていますが、相当ハードルが高くなりますよ?
VBでできることならVBで書いてしまえばよさそうですが。
ちょっと補足
まず、C言語のみを考えて
long openfile_c(const char *filename,long* n,double** data);
のような関数を作成する。
でC言語からは
double* data;
long n;
if (openfile_c("hoge.txt", &n, &data) == 0)
{
/* data[0], data[1] のように配列同様に扱える。上限は n */
}
/* mallocしたのでfreeしておく */
free(data);
のように使えるように関数を実装する。
次の段階でVBとの連携を考慮した関数
EXTERN long __stdcall openfile(const char* filename, LPSAFEARRAY* psa);
のような関数を作成する。
その関数の中で、openfile_cをよび、SAFEARRAYを作成する処理を入れる。
(こうしたほうが絶対わかりやすいと思う。)
>ちなみに、なんでこんな単純な作業をDLLにするのでしょうか?
実際にやりたいことはもっと複雑で,まずは簡単な例をと思ったのですが,それもうまくいかず…
いろいろありがとうございます.
SafeArrayCreateについて調べてみます.
あともう一つ質問があるんですけど,
VB側で配列を作成し,その配列をDLLに渡し,DLL内で処理をした配列をVBで受け取ることは可能なのでしょうか.
例えば,data[100]という配列をVBで作成し,DLLに渡し,DLL内で
data2[i]=data[i]+i;
みたいな処理をして,data2をVBに渡した時に
VBからdata2はうまく読み取れるのでしょうか.
よろしくお願いします.
>例えば,data[100]という配列をVBで作成し,DLLに渡し,DLL内で
>data2[i]=data[i]+i;
>みたいな処理をして,data2をVBに渡した時に
>VBからdata2はうまく読み取れるのでしょうか.
>よろしくお願いします.
できます。
配列の中身を変更することも可能です。
ですが、一番最初のように配列を作ったり、配列数を増やしたり減らしたりすることは
簡単にできません。
Sample)
VB
Private Declare Sub Hoge Lib "XXX.dll" (ByRef ary As Double, ByVal cnt As Long)
Dim data(9) As Double
' 適当に値を入れる
Hoge data(0), 10
' ここで dataの中身を確認してみるとか。
VC
void WINAPI Hoge(double* data, const int cnt)
{
int i;
char buff[8];
for (i = 0; i < cnt; ++i)
{
/* 値をMessageBoxで表示してみる */
sprintf(buff, "%lf", data[i]);
MessageBoxA(NULL, buff, "TEST", MB_OK);
/* 値を変更してみる */
data[i] += 10.0;
}
}
%lfなんで
>char buff[8];
では文字数が少なかったかも。32くらいにしておいてください。
1次元配列においてはうまく実行できました.
ありがとうございました.
しかし,2次元配列にするとうまくできません.
2次元配列のときも可能なのでしょうか.
実際に自分が2次元配列のさいにやった処理は
VB
Private Declare Sub Hoge Lib "XXX.dll" (ByRef ary As Double, ByVal cnt As Long)
Dim data(2, 2) As Double
’適当な値を入れる.
Hoge (data(0,0), 3))
VC
void WINAPI Hoge(double* data, const int cnt)
{
int i;
for (i = 0; i < cnt; ++i)
{
for(i=0;i<cnt;i++)
{
c[0][i]=10+i;
c[1][i]=20+i;
c[2][i]=30+i;
}
}
}
このような処理をすると配列に違う値が入ります.
どこかに間違いがあるのでしょうか.
ちなみに2次元配列はVBではdata(1,2)と書き,VCではdata[1][2]と書くので間違いないでしょうか.
よろしくお願いします.
2次元配列を扱えるか微妙ですが
とりあえず、
>VBではdata(1,2)と書き,VCではdata[1][2]
ではないですね。
data(1, 2)であれば、data[2][3]でしょう。
(VBとC++では配列の下限値が違う。)
それと、そろそろデバッガを使ってみてはどうでしょうか?
VB側でexeを作成し、VC側のDLLのデバッグの実行プログラムとして指定すれば
デバッグできます。
指定の関数内にブレイクポイントでも張っておけばF5実行でそこにとまるでしょう。
また、レスが遅れるならあらかじめいってもらえると助かります。
(気長なほうではないので。1週間レスがないとカナリ回答する気なえますから。)
>2次元配列を扱えるか微妙ですが
やってみました。
2次元配列の場合、double* をVBから受けるので
>data[2][3]
のような記述は出来ません。(double**のときになる)
そこで2次元配列はメモリ上1直線で扱われるというのがVBでも出来れば
いけます。
(C言語の基本)
http://www9.plala.or.jp/sgwr-t/c/sec09.html
で、試しに
void WINAPI Sample(double* data, const int row, const int col)
{
double d = 1.0;
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
data[(i * col) + j] = d++;
}
}
}
Private Declare Sub Sample Lib "VBDLL.dll" (data As Double, ByVal r As Long, ByVal c As Long)
Sub test()
Dim d(2, 3) As Double
Dim i As Integer, j As Integer
Sample d(0, 0), 3, 4
For i = 0 To 2
For j = 0 To 3
Debug.Print d(i, j)
Next
Next
End Sub
ってのを実行してみると、どうも
>data(1, 2)であれば、data[2][3]でしょう。
のような関係ではなさそうです。
イミディエイトウィンドウをみて確認してみてください。
分かりました.ありがとうございました.
> そろそろデバッガを使ってみてはどうでしょうか?
デバッガについて調べてみます.ありざとうございます.
> レスが遅れるならあらかじめいってもらえると助かります。
言い訳になりますが,パソコンの調子が悪くて,作業が進まなかったもので.
本当にすみません.
Blueさん,本当にありがとうございました.
こっちの方がわかりやすそう
void WINAPI Sample(double* data, const int row, const int col)
{
double d = 1.0;
TCHAR buff[256];
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
wsprintf(buff, TEXT("(%d, %d) : %d"), i, j, (int)(*data));
::MessageBox(NULL, buff, TEXT(""), MB_OK);
data++;
}
}
}
Sub test()
Dim d(2, 3) As Double
Dim i As Integer, j As Integer
Dim n As Double
n = 1
For i = 0 To 2
For j = 0 To 3
d(i, j) = n
n = n + 1
Next
Next
Sample d(0, 0), 3, 4
End Sub
ということで、どうもメモリの配置がVBとC++では違うようです。
上のサンプルより、VBでは
d(0, 0)→d(1, 0)→d(2, 0)→・・・→d(0, 3)→d(1, 3)→d(2, 3)
というメモリ配置のようです。
C++側もこれを考慮して
>data[(i * col) + j]
の[]の中を代えればいいでしょう。(ここは考えてください。)
なんどもすみません.
ありがとございます.あとは自分でやってみます.
また困った時はよろしくお願いします.
ツイート | ![]() |