VBからVC++6.0で作ったDLLの関数に配列を渡して、演算した答えの配列を返すにはどうすればいいのでしょうか。。
VB側とVC側のサンプルなどがありましたら、教えてください。
VC++にとって簡単な方法は、「VB側で、配列の先頭を参照渡しする」ようにする事です。
VBにとって簡単な方法は、「C++ DLL側で、SafeArrayを使う」ようにする事です。
> VB側とVC側のサンプルなどがありましたら、教えてください。
http://www.microsoft.com/japan/msdn/vs_previous/vbasic/docs/dll/#8
横レスですみません
質問の文面からすると渡す配列は数字ではないかと。
もし数字なら
呼び出す方は(VBは)
Private Type struct
arg(0 To 9) As Long '配列は適当に10個に
End Type
Private Declare Sub GetArg Lib "getarg.dll" (ByRef arg As struct)
Private Sub Command1_Click()
Dim st As struct
Call GetArg(arg)
End Sub
受け取る方は(VC++)
struct st{
long arg[10];
};
void __declspec(dllexport)__stdcall GetArg(struct st* st1);
void __declspec(dllexport)__stdcall GetArg(struct st* st1)
{
int n;
for(n=0 ; n<10 ; n++){
st1->arg[n] += 10; //とりあえず何かする
}
}
こんな方法で渡ります。
もし配列が文字列なら、私なら色色面倒なのでマップドメモリーで渡します。
魔界の仮面弁士さん、ねろさんありがとうございました。
ねろさんのサンプルを試してみたのですが、Call GetArg(arg)のところでargの変数が定義されていません とエラーがでてしまいました。どうしてでしょ。。
あとユーザー定義型の変数で渡してるようですが、普通に「dim a(10) as long」と宣言して使えないのでしょうか?
Private Declare Function Test2 Lib "abc.dll" _
ByVal IntA As Long, ByVal IntB As Integer, ByRef intAns As Integer) _
As Integer
sub AAA()
dim iA(10) as long
dim intAns(10) as long
dim ret as intger
ret = Test2(iA, 5, intAns)
end sub
あと参考書には
Q_KENSA_API int __stdcall Test2(int iA*, int iB, int *Ans)
{
return 0;
}
でVC++側のサブルーチンを作っているのですが、これじゃまずいですか?
魔界の仮面弁士の教えていただいたサンプルはちょっと難しそうなので今解読中です・・・汗
VB側のバージョンが書かれていなかったので、 VC側のバージョンと同様、
Visual Basic 6.0と仮定して回答します。
(VB.NETの場合は、マーシャリングについて調べて見てください)
> Call GetArg(arg)のところでargの変数が定義されていません
> とエラーがでてしまいました。どうしてでしょ。。
文字どおりの意味だと思いますよ。
「argという名前の変数が定義されていないから」ですよね。
今回の場合はおそらく、GetArg(st)の書き間違いだったのかと。
> 普通に「dim a(10) as long」と宣言して使えないのでしょうか?
上記の場合、普通は a(0)〜a(10)までも 11個分の配列になりますね。
これらを、SafeArrayのまま DLLに渡すのであれば、
Declare Sub foo Lib "bar.dll" (ByRef x() As Long)
のように書いて、
Dim a(10) As Long
Call foo(a)
のように呼び出す事になります。DLL側は、
void __declspec(dllexport)__stdcall foo(LPSAFEARRAY FAR *ppsa)
という感じですかね。
一方、DLL側がSafeArrayではなく、
void __declspec(dllexport)__stdcall foo(DWORD * ppsa[])
のような普通の配列の場合は、
Declare Sub foo Lib "bar.dll" (ByRef x As Long)
と書いて、
Dim a(10) As Long
Call foo(a(0))
という感じにするか、もしくは、
Declare Sub foo Lib "bar.dll" (ByVal x As Long)
と書いて、
Dim a(10) As Long
Call foo(VarPtr(a(0)))
のように呼び出す事になるかと思います。
# なお、私自身、C++は専門外なので、上記のDLL側コードが、正しい文法に
# なっているのかどうかを検証する事はできません。m(_ _;)m
魔界の仮面弁士さんのフォローの通り書き間違いです。
Call GetArg(arg) -> GetArg(st) 深謝 ^^;
魔界の仮面弁士さんいつもすみません
>Private Declare Function Test2 Lib "abc.dll" _
>ByVal IntA As Long, ByVal IntB As Integer, ByRef intAns As Integer) _
> As Integer
これですが ByVal IntA As Longの前に"(" が抜けています。
それと
ByVal IntA はByRfe IntAでは?
ByValは値そのものを,ByRefは先頭のアドレスを送ります。
配列の場合は当然先頭のアドレスを送りますから、普通はByRefですよね。
>Q_KENSA_API int __stdcall Test2(int iA*, int iB, int *Ans)
ですが
Q_KENSA_API int __stdcall Test2(int *iA, int iB, int *Ans)
でしょう
こういった配列と数字が混じったものほどユーザー定義型と構造体は
相性がいいんですが。
魔界の仮面弁士さん、ねろさんお世話になってます。m(_ _)m
ねろさんのサンプル動きました。どうもです。。普通の配列(整数の)に
してみても動きました。普通はユーザー定義型の方で使うのでしょうか?
いろいろ調べてて違うものも試したのですが、これでも大丈夫でしょうか?↓
メモリうんぬんという問題?で違う値が返ってくると書いてあったような・・
それを確認しようとしたのですが、どこに書いてあったのかわからなくなっ
てしまいました(泣)ご指摘いただければ幸いです。
-- VC側 ---------------
ABC_API int __stdcall Test(int *iA, int iB, int *Ans)
{
int iRet;
for (int i = 0; i < 4; i++) {
Ans[i] = iA[i] + iB;
}
iRet=1;
return iRet;
}
-- VB側 ---------------
Private Declare Function Test Lib "ABC.dll" _
(ByRef IntA As Long, ByVal IntB As Long, ByRef intAns As Long) As Long
Dim iA(4) As Long
Dim iAns(4) As Long
Dim ret As Long
Dim i As Integer
iA(0) = 12
iA(1) = 5
iA(2) = 23
iA(3) = 44
iA(4) = 44
ret = Test(iA(0), 100, iAns(0))
For i = 0 To 4
Debug.print "(" & i & ") " & iA(i) & " , " & iAns(i)
Next i
魔界の仮面弁士さんのご提示していただいたサンプルは、まだ解析中です。
SAFEARRAYが解らなかったので、これから試してみようと思ってます。
環境はVB6.0、VC++6.0 + VisualStudioSP5です。(便乗の質問ですが
バージョンの確認でVBではSP5とでるのにVC++では出ないのでしょうか?
Full版当てたと思ったのですが。。)
いろいろな方法があったのでどういう風にやるのがいいか、初心者には迷っ
てしまいます^^;
魔界の仮面弁士さん、ねろさんご丁寧なご指導ありがとうございました。
とりあえずは目的の動作が確認できたので「解決」にチェックさせていただ
きました。また何かありましたらよろしくお願いいたします。。
上記のソースのご指摘もお時間ありましたらよろしくです。。m(_ _)m
あぁ。。つけたつもりがチェック忘れてしまいました。。ごめんなさい。。
> こういった配列と数字が混じったものほどユーザー定義型と構造体は
> 相性がいいんですが。
ただしVB6ではアライメントが4バイトに固定されているので、
メンバ定義には留意する必要がありますね。
例えば以下の2つのユーザー定義型( UDT1 と UDT2 )は、
単にメンバの順番が変更されているだけですが、それによって、
LenとLenBの結果が異なってしまうので、DLL側のアライメントも
一致させておかないと、データがずれてしまう可能性があります。
Option Explicit
Private Type UDT1
A As Integer
B As Byte
C As Byte
D(2) As Long
End Type
Private Type UDT2
A As Integer
B As Byte
D(2) As Long
C As Byte
End Type
Private Sub Command1_Click()
Dim X As UDT1
Dim Y As UDT2
Debug.Print Len(X), LenB(X) '16 16 と表示される
Debug.Print Len(Y), LenB(Y) '16 20 と表示される
End Sub
> いろいろ調べてて違うものも試したのですが、これでも大丈夫でしょうか?↓
配列のサイズが間違っています。DLL側を
> for (int i = 0; i < 4; i++) {
から
for (int i = 0; i <= 4; i++) {
に修正してください。
C/C++ で、int a[5]; と宣言した時は、〜a[4]までの配列を意味しますが、
VBで Dim a(5) As Long と宣言した時は、〜a(5)までを意味します。
>普通はユーザー定義型の方で使うのでしょうか?
うまく引数が渡ればどの方法でもいいのでしょう、
ユーザー定義型を使用するのは半分は趣味で半分は
プログラムの見易さです。
>これでも大丈夫でしょうか?↓
た、多分。^^;
ただし
VC側に int i;
はいるでしょう。
VB側は
>ret = Test(iA(0), 100, iAns(0))
>For i = 0 To 4
> Debug.print "(" & i & ") " & iA(i) & " , " & iAns(i)
>Next
でなく
For i = 0 To 4
ret = Test(iA(0), 100, iAns(0))
Debug.print "(" & i & ") " & iA(i) & " , " & iAns(i)
Next
でしょう
> でなく
> For i = 0 To 4
> ret = Test(iA(0), 100, iAns(0))
> Debug.print "(" & i & ") " & iA(i) & " , " & iAns(i)
> Next
> でしょう
ん……。 5回も呼ぶ必要は無いのでは?
実際にDLLを作って試してみたところ、元のコードの場合は
(0) 12 , 112
(1) 5 , 105
(2) 23 , 123
(3) 44 , 144
(4) 44 , 0
という結果が出力され、私の回答『for(int i = 0; i <= 4; i++)』の
修正を行ったバージョンの場合は、以下のようになりました。
(0) 12 , 112
(1) 5 , 105
(2) 23 , 123
(3) 44 , 144
(4) 44 , 144
そのとおりです
間違えました! 汗;
魔界の仮面弁士さん、ねろさん再びありがとうございます。
int i;忘れてました。int a[5]; とDim a(5)も気をつけます^^;
本当にどうもありがとうございました。
ツイート | ![]() |