DLLを使用する際にDLL内でファイルを開くには

解決


nao  2007-01-19 09:50:44  No: 97691

Visual C++6.0で作成したDLLをVisualbasic6.0で使用しています.
ファイル名を渡すとそのファイルの中にあるデータの数を出すというDLLを作成しました.
このDLLはVisual  C++では正常に使用できたので正しいと思います.

このDLLをVisual  Basicで使用すると,うまくいきません.

DLLの中でもしファイルをopenできなかったらreturnを1で返すようにしているのですが,ファイル名を渡すとreturnが1となってしまいます.
これはどうしてでしょうか.


魔界の仮面弁士  2007-01-19 12:02:59  No: 97692

これだけの情報では、答えようが無いと思いますけど。(^^;

> このDLLはVisual  C++では正常に使用できたので正しいと思います.
> このDLLをVisual  Basicで使用すると,うまくいきません.

可能性1) C++的には正しいのだが、VB向けの設計にはなっていなかった。
可能性2) DLL自体は正しいが、Declare 宣言に問題があった。
可能性3) DLL自体は正しいが、VBからの呼び出し処理が適切ではなかった。
可能性4) DLL自体に問題があり、C++で正常動作しているようにみえたのは偶然。

> ファイル名を渡すとreturnが1となってしまいます.
実際のコード等を見ないと何とも言えませんが、思いつくところでは、
  ・フルパス/相対パスの指定が考慮されていなかった。
  ・C++からの場合と、VBからの場合とで、カレントディレクトリが異なっていた。
  ・引数指定に問題があり、DLL側で適切なファイル名文字列が渡っていなかった。
とか……。


nao  2007-01-20 01:38:40  No: 97693

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 []);

としています.
どこか処理がおかしいところがあるでしょうか.
よろしくおねがいします.


Blue  2007-01-20 02:49:17  No: 97694

とりあえず、
>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は書いておいたほうがよさそう。


Blue  2007-01-20 02:50:37  No: 97695

誤字
>(配列で割り取りするならば
(配列でやり取りするならば
# なぜ「や」が「わ」になったんだろ、、、orz


Blue  2007-01-20 02:55:55  No: 97696

あ〜よく見たらだめやんけ。
>    Dim ret, n, data() As Double
VB側で配列を用意できない場合、VC側でmallocやらで領域を設定しないとだめかなぁ、、、
(保障ナシ。配列型ではなく確保したポインタを指すようにするならば出来そうだけど)

SAFEARRAYならCreateSafeArrayあたりで配列を作成できるけど。


nao  2007-01-20 04:42:07  No: 97697

たしかに「インデックスが有効範囲にありません」というエラーが発生しました.
mallocもうまくいきませんでした.

そこでSAFEARRAYというのを使おうと思ったのですが,調べてもよく分かりません.
longやdoubleの変わりにSAFEARRAYを使うのでしょうか.
上のプログラムでいうとどのような処理をするのでしょうか.
簡単な例などありましたら,おしえてください.
よろしくお願いします.


Blue  2007-01-20 05:54:20  No: 97698

とりあえず、ある程度VB側で配列を用意するならば
最初の指摘のとおりの変更で動くでしょう。
(そういう場合はC側に配列数の上限を渡すべき。ポインタでは終端がわからないので
バッファオーバラン(メモリを確保していない領域を参照して落ちる)が起こる可能性があります。)

SAFEARRAYを使うとなると、SafeArrayCreateという関数を使って
配列を作ります。
ただ、C側で配列数が固定でないので、毎回ReDimとなるとコストが高いでしょう。
ですので、C側でファイルをすべて読み込む処理を作りこんでから
配列を作るようなつくりにすべきでしょう。

まずは、SAFEARRAYをつかうのではなく、C言語の動的配列の使い方を
調べてください。
(mallocやreallocあたりを使う。MFCならCArrayとかでいけるかな。)

ちなみにSafeArrayCreateで過去ログ検索やGoogle検索すればサンプルはいくらでも見つかると思います。

ということで、この質問はVBの質問ではなくなりますね。

ちなみに、なんでこんな単純な作業をDLLにするのでしょうか?
いままで書いてきていますが、相当ハードルが高くなりますよ?
VBでできることならVBで書いてしまえばよさそうですが。


Blue  2007-01-20 06:02:45  No: 97699

ちょっと補足

まず、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を作成する処理を入れる。

(こうしたほうが絶対わかりやすいと思う。)


nao  2007-01-20 06:25:36  No: 97700

>ちなみに、なんでこんな単純な作業をDLLにするのでしょうか?
実際にやりたいことはもっと複雑で,まずは簡単な例をと思ったのですが,それもうまくいかず…

いろいろありがとうございます.
SafeArrayCreateについて調べてみます.

あともう一つ質問があるんですけど,
VB側で配列を作成し,その配列をDLLに渡し,DLL内で処理をした配列をVBで受け取ることは可能なのでしょうか.

例えば,data[100]という配列をVBで作成し,DLLに渡し,DLL内で
data2[i]=data[i]+i;
みたいな処理をして,data2をVBに渡した時に
VBからdata2はうまく読み取れるのでしょうか.
よろしくお願いします.


Blue  2007-01-20 06:35:23  No: 97701

>例えば,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;
    }
}


Blue  2007-01-20 06:37:45  No: 97702

%lfなんで
>char buff[8];
では文字数が少なかったかも。32くらいにしておいてください。


nao  2007-01-24 01:18:47  No: 97703

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]と書くので間違いないでしょうか.
よろしくお願いします.


Blue  2007-01-24 01:58:38  No: 97704

2次元配列を扱えるか微妙ですが

とりあえず、
>VBではdata(1,2)と書き,VCではdata[1][2]
ではないですね。

data(1, 2)であれば、data[2][3]でしょう。
(VBとC++では配列の下限値が違う。)

それと、そろそろデバッガを使ってみてはどうでしょうか?
VB側でexeを作成し、VC側のDLLのデバッグの実行プログラムとして指定すれば
デバッグできます。
指定の関数内にブレイクポイントでも張っておけばF5実行でそこにとまるでしょう。

また、レスが遅れるならあらかじめいってもらえると助かります。
(気長なほうではないので。1週間レスがないとカナリ回答する気なえますから。)


Blue  2007-01-24 02:15:58  No: 97705

>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]でしょう。
のような関係ではなさそうです。
イミディエイトウィンドウをみて確認してみてください。


nao  2007-01-24 02:26:35  No: 97706

分かりました.ありがとうございました.

> そろそろデバッガを使ってみてはどうでしょうか?
デバッガについて調べてみます.ありざとうございます.

> レスが遅れるならあらかじめいってもらえると助かります。
言い訳になりますが,パソコンの調子が悪くて,作業が進まなかったもので.
本当にすみません.
Blueさん,本当にありがとうございました.


Blue  2007-01-24 02:31:54  No: 97707

こっちの方がわかりやすそう

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] 
の[]の中を代えればいいでしょう。(ここは考えてください。)


nao  2007-01-24 05:55:18  No: 97708

なんどもすみません.
ありがとございます.あとは自分でやってみます.
また困った時はよろしくお願いします.


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加