[リストへもどる]   [VBレスキュー(花ちゃん)]
一括表示

投稿時間:2006/04/14(Fri) 11:24
投稿者名:morimori
Eメール:
URL :
タイトル:
VC++で作成したDLLにユーザ定義型の配列を指定したい
はじめまして、morimoriと申します。

現在、VC++で作成したDLLがあり、このDLLをVBから使用したいと思っているのですが、
そこでDLLの引数にユーザ定義型の配列を指定し、DLLで複数のデータを設定してVBに返すというプロ
グラムを作成したいのですが、ユーザ定義型を配列型にするとデータを取得することができません。
以下にソースを提示しますので、ご教授の程、宜しくお願いします。

[VB側]
Private Type ReceiveInfo
    lNumber As Long
    cReceiveData As String * 100
End Type

Private Declare Function dllReceive Lib "dllWinSock.dll" (ByRef tReceiveData() As
ReceiveInfo) As Long

Private Sub Command3_Click()
    Dim i As Integer
    Dim tReceiveData(20) As ReceiveInfo
    
    dllReceive tReceiveData()
    
    Text2.Text = ""
    For i = 0 To UBound(tReceiveData)
        If tReceiveData(i).cReceiveData <> "" Then
            Text2.Text = Text2.Text & tReceiveData(i).cReceiveData & ", "
        End If
    Next i
    
End Sub


[VC側]
typedef struct{
    int  iNumber;
    char cReceiveData[100];
} ReceiveInfo;

extern "C" int WINAPI EXPORT dllReceive(ReceiveInfo *tReceiveInfo)
{
    int i;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    for(i=0;i<g_iReceiveCnt;i++){
        tReceiveInfo[i].iNumber = i;
        strcpy(tReceiveInfo[i].cReceiveData, g_cReceiveData[i]);
        strcpy(g_cReceiveData[i], "");
    }
    g_iReceiveCnt = 0;

    return 0;
}


実際のプログラムとは一部、異なりますが上記のことを
実現したいと思っております。
現状では、DLLから値が返った後、変数の値を確認しても値が何も設定されない状態です。

配列にせずにした場合は値を取得することが可能です。
宜しくお願いします。

投稿時間:2006/04/14(Fri) 11:36
投稿者名:とも
Eメール:
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
気になった点
VBでByRefで配列を渡しているんだからCで受けるほうは
ReceiveInfo **tReceiveInfo
なんじゃないの?

投稿時間:2006/04/14(Fri) 11:50
投稿者名:K.J.K.
Eメール:akiya@koalanet.ne.jp
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
もし、

> [VB側]
> Private Type ReceiveInfo
>     lNumber As Long
>     cReceiveData As String * 100
> End Type
>
> Private Declare Function dllReceive Lib "dllWinSock.dll"  _
> (ByRef tReceiveData() As ReceiveInfo) As Long

ならば、C側では、
extern "C" int WINAPI EXPORT dllReceive(SAFEARRAY ** ppsa)
という形式である必要があります。

C側では
> typedef struct{
>     int  iNumber;
>     char cReceiveData[100];
> } ReceiveInfo;
>
> extern "C" int WINAPI EXPORT dllReceive(ReceiveInfo *tReceiveInfo)

なのだから、

Private Type ReceiveInfo
    lNumber As Long
    cReceiveData(0& To 99&) As Byte
End Type

Private Declare Function dllReceive Lib "dllWinSock.dll"  _
(ByRef tReceiveData As ReceiveInfo) As Long

Private Sub Command3_Click()
    Dim i As Integer
    Dim tReceiveData(0& To 19&) As ReceiveInfo
    
    dllReceive tReceiveData(0&)

    Text2.Text = ""
    For i = 0 To UBound(tReceiveData)
        If tReceiveData(i).cReceiveData <> "" Then
            Text2.Text = Text2.Text & tReceiveData(i).cReceiveData & ", "
        End If
    Next i
    
End Sub

とでもするのでは。

投稿時間:2006/04/14(Fri) 13:01
投稿者名:morimori
Eメール:
URL :
タイトル:
Re^2: VC++で作成したDLLにユーザ定義型の配列を指定したい
とも様、KJ.K様
ご返答ありがとうございます。

確かにDLL側はダブルポインタにしなければいけませんね。
初歩的なミスで申し訳ございません。

> extern "C" int WINAPI EXPORT dllReceive(SAFEARRAY ** ppsa)
「SAFEARRAY」の型にしなければいけないのですか?
ということは、その後に型変換する必要があるということでしょうか?

お手数ですが、ご教授いただけると助かります。
宜しくお願いします。


> もし、
>
> > [VB側]
> > Private Type ReceiveInfo
> >     lNumber As Long
> >     cReceiveData As String * 100
> > End Type
> >
> > Private Declare Function dllReceive Lib "dllWinSock.dll"  _
> > (ByRef tReceiveData() As ReceiveInfo) As Long
>
> ならば、C側では、
> extern "C" int WINAPI EXPORT dllReceive(SAFEARRAY ** ppsa)
> という形式である必要があります。
>
> C側では
> > typedef struct{
> >     int  iNumber;
> >     char cReceiveData[100];
> > } ReceiveInfo;
> >
> > extern "C" int WINAPI EXPORT dllReceive(ReceiveInfo *tReceiveInfo)
>
> なのだから、
>
> Private Type ReceiveInfo
>     lNumber As Long
>     cReceiveData(0& To 99&) As Byte
> End Type
>
> Private Declare Function dllReceive Lib "dllWinSock.dll"  _
> (ByRef tReceiveData As ReceiveInfo) As Long
>
> Private Sub Command3_Click()
>     Dim i As Integer
>     Dim tReceiveData(0& To 19&) As ReceiveInfo
>    
>     dllReceive tReceiveData(0&)
>
>     Text2.Text = ""
>     For i = 0 To UBound(tReceiveData)
>         If tReceiveData(i).cReceiveData <> "" Then
>             Text2.Text = Text2.Text & tReceiveData(i).cReceiveData & ", "
>         End If
>     Next i
>    
> End Sub
>
> とでもするのでは。

投稿時間:2006/04/14(Fri) 14:04
投稿者名:K.J.K.
Eメール:akiya@koalanet.ne.jp
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
# 引用は適切に。全文を引用するのは無意味です。

> > extern "C" int WINAPI EXPORT dllReceive(SAFEARRAY ** ppsa)
> 「SAFEARRAY」の型にしなければいけないのですか?
> ということは、その後に型変換する必要があるということでしょうか?

意味がわかっています? 今回はこちらではないはずですよ。

投稿時間:2006/04/14(Fri) 14:26
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^2: VC++で作成したDLLにユーザ定義型の配列を指定したい
>     dllReceive tReceiveData(0&)
これってできましたか?
ユーザ定義型は、他のDouble型配列や、Long型配列と、配列の持ち方が違うように思ったのですけど
どうでしょうか?
現在のアドレス+1 が次の配列のアドレスを指すのでしたっけ?
(いまは試せる環境がないのでなんとも、、、)

投稿時間:2006/04/14(Fri) 14:52
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^3: VC++で作成したDLLにユーザ定義型の配列を指定したい
あ、スイマセン。

Byte型配列ならなんかうまくいきそうですね。
String型のメンバ変数があるとうまくいかないです。

投稿時間:2006/04/14(Fri) 13:03
投稿者名:Blue
Eメール:
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
過去ログ参考にしてください。
hhttp://www.bcap.co.jp/hanafusa/vbbbs/wforum.cgi?mode=allread&no=5900

> Private Type ReceiveInfo
>     lNumber As Long
>     cReceiveData As String * 100
> End Type
固定長文字列はVC側では扱いにくいので、変更したほうが良いかも。

投稿時間:2006/04/14(Fri) 13:24
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^2: VC++で作成したDLLにユーザ定義型の配列を指定したい
補足

固定長文字列を設定する場合は、

SafeArrayGetElement

で、格納する構造体を取り出して、
wcsncpy(またはwcscpy)で格納し、

SafeArrayPutElement

で、構造体を配列に設定します。

固定長でない場合は、wcsncpy(またはwcscpy)ではなくて、SysAllocStringあたりを使います。

ついでにここも参考にしてください。(↓は結構詳しく解説したつもりです)
hhttp://c-chat.net/modules/newbb/viewtopic.php?topic_id=309&forum=2&noreadjump=1

投稿時間:2006/04/14(Fri) 17:00
投稿者名:morimori
Eメール:
URL :
タイトル:
Re^3: VC++で作成したDLLにユーザ定義型の配列を指定したい
Blue様、K.J.K様
ご返答ありがとうございます。

> ついでにここも参考にしてください。(↓は結構詳しく解説したつもりです)
> hhttp://c-chat.net/modules/newbb/viewtopic.php?topic_id=309&forum=2&noreadjump=1Blue様の参考
ページなどを見ており、そう簡単にはいかないのだなと
思いました。
いろいろ試してはいるのですが、なかなかうまくいっていないというのが現状です。

現状のソースを提示しますのでご教授いただければと思います。

[VB側]
Private Type ReceiveInfo
    cReceiveData(100) As Byte
End Type

Private Declare Function dllReceive Lib "dllWinSock.dll" (ByRef tReceiveData() As
ReceiveInfo) As Long

Private Sub Command3_Click()
    Dim i As Integer
    Dim j As Integer
    Dim cBuf As String
    Dim tReceiveData(20) As ReceiveInfo
    
    dllReceive tReceiveData()
    
    Text2.Text = ""
    For i = 0 To UBound(tReceiveData)
        If tReceiveData(i).cReceiveData <> "" Then
            Text2.Text = Text2.Text & tReceiveData(i).cReceiveData & ", "
        End If
    Next i
    
End Sub




[VC側]
extern "C" int WINAPI EXPORT dllReadFindUid(char *pcIpAddress, LPSAFEARRAY *ppsa)
{
    long i;
    SAFEARRAY *psa = *ppsa;
    ReceiveInfo tReceiveInfo;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    SafeArrayLock( psa );
    g_iReceiveCnt = 1;
    for(i=0;i<g_iReceiveCnt;i++){
        SafeArrayGetElement(psa, &i, &tReceiveInfo);
        strcpy(tReceiveInfo.cReceiveData, g_cReceiveData[i]);
        strcpy(g_cReceiveData[i], "");
        SafeArrayPutElement(psa, &i, &tReceiveInfo);
    }
    g_iReceiveCnt = 0;
    SafeArrayUnlock( psa );

    return 0;
}

投稿時間:2006/04/14(Fri) 17:13
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^4: VC++で作成したDLLにユーザ定義型の配列を指定したい
> hhttp://c-chat.net/modules/newbb/viewtopic.php?topic_id=309&forum=2&noreadjump=1Blue様の参考
のソースを参考にしたんではないのでしょうか?

> いろいろ試してはいるのですが、なかなかうまくいっていないというのが現状です。
どううまくいかないのでしょうか?

結局DLLは何をするんでしょうか?WinSockって?
g_cReceiveDataはなに(文字列ですよね)?


ていうか、
K.J.K.さんの
> hhttp://www.bcap.co.jp/hanafusa/vbbbs/wforum.cgi?no=6254&reno=6251&oya=6248&mode=msgview
をよみましたか?

SafeArrayを使う場合は、DLLだけの修正になって、
K.J.K.さんのVBのコードをコピペするならば、DLLの修正はいりませんよ。
(ただ、Byte型配列メンバ変数を使用するときはg_cReceiveDataがShift_JISの文字列であれば、変換が必要かな)

投稿時間:2006/04/14(Fri) 18:04
投稿者名:morimori
Eメール:
URL :
タイトル:
Re^5: VC++で作成したDLLにユーザ定義型の配列を指定したい
k.J.K様の説明をしっかり理解していませんでした。
申し訳ありません。

ソースとしては、DLL側の方を修正しなくてもよいのであれば極力修正はしない形で進めればと思ってお
ります。
VBから引数に設定する際、構造体の配列を渡したいので、
以下のソースに変更しました。

Private Type ReceiveInfo
    lNumber As Long
    cReceiveData As Byte(0 To 99)
End Type


Private Declare Function dllReceive Lib "Test.dll" _
(ByRef tReceiveData() As ReceiveInfo) As Long

Private Sub Command3_Click()
    Dim i As Integer
    Dim tReceiveData(0 To 19) As ReceiveInfo
    
    dllReceive tReceiveData()
    
    Text2.Text = ""
    For i = 0 To UBound(tReceiveData)
        If tReceiveData(i).cReceiveData <> "" Then
            Text2.Text = Text2.Text & tReceiveData(i).cReceiveData & ", "
        End If
    Next i
    
End Sub




extern "C" int WINAPI EXPORT dllReceive(ReceiveInfo **tReceiveInfo)
{
    int i;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    for(i=0;i<g_iReceiveCnt;i++){
        tReceiveInfo[i].iNumber = i;
        strcpy(tReceiveInfo[i].cReceiveData, g_cReceiveData[i]);
        strcpy(g_cReceiveData[i], "");
    }
    g_iReceiveCnt = 0;

    return 0;
}


上記のソースで実行すると、VB側で関数を実行した後、「オーバーフローしました」
のメッセージが表示されます。

投稿時間:2006/04/14(Fri) 20:27
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^6: VC++で作成したDLLにユーザ定義型の配列を指定したい
配列ではなくて、配列の最初の要素を渡さないとダメなのでは?K.J.K.さんのはそうなっているけど。

> Private Declare Function dllReceive Lib "Test.dll" _
> (ByRef tReceiveData() As ReceiveInfo) As Long
Private Declare Function dllReceive Lib "dllWinSock.dll" _
(ByRef tReceiveData As ReceiveInfo) As Long

> dllReceive tReceiveData()
dllReceive tReceiveData(0&)


> 結局DLLは何をするんでしょうか?WinSockって?
> g_cReceiveDataはなに(文字列ですよね)?
の説明があれば、SafeArrayのやつを考えようと思ったのですが、ないのでやりません。

投稿時間:2006/04/15(Sat) 09:48
投稿者名:K.J.K.
Eメール:akiya@koalanet.ne.jp
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
# 仕切りなおし。

まず、仕様を整理する必要があるでしょう。
1,DLL側からVBに渡す構造体は、固定長の配列として渡されるのか?
  それとも、そのときに応じて長さが変わる可変長の配列なのか?
2,DLL側からVBに渡す構造体の内部の文字列について、固定長なのか
  可変長なのか?

で、敢えて両方とも可変長にするのならば、

Private Type ReceiveInfo
    lNumber As Long
    cReceiveData As String
End Type

Private Declare Function dllReceive Lib "dllWinSock.dll" _
(ByRef tReceiveData() As ReceiveInfo) As Long

typedef struct{
    int  iNumber;
    BSTR cReceiveData; // VBの文字列はBSTR
} ReceiveInfo;

extern "C" int WINAPI EXPORT dllReceive(SAFEARRAY **ppsa)
{
    int i;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    SAFEARRAY *psa;
    HRESULT hresult = SafeArrayAllocDescriptor(1, &psa);
    if( FAILED( hresult ) ) return 1;

    psa->rgsabound[ 0 ].lLbound = 0;
    psa->rgsabound[ 0 ].cElements = g_iReceiveCnt;
    psa->cbElements = sizeof(ReceiveInfo);
    hresult = SafeArrayAllocData(psa);
    if( FAILED( hresult ) ) return 1;

    // ここ以降で、psa->pvDataをReceiveInfo*にキャストするなりして
    // 中身を入れていく。
    // 文字列領域の作成にはSysAllocStringByteLenを用いること。
    // 以下略。

    return 0;
}

投稿時間:2006/04/15(Sat) 12:19
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^2: VC++で作成したDLLにユーザ定義型の配列を指定したい
構造体の場合、String型のコード変換が行われないので、
>    // 文字列領域の作成にはSysAllocStringByteLenを用いること。
SysAllocStringでないとだめなんではないでしょうか?(検証済み)
# SysAllocStringByteLenはShift_JISコードの文字列領域を作成し、設定する。

でも、DLL側ではShift_JISコードで持っているので、DLL側で Unicode変換するよりも、
VB側で StrConv で変換するほうが良いかも。

投稿時間:2006/04/15(Sat) 13:22
投稿者名:K.J.K.
Eメール:akiya@koalanet.ne.jp
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
> 構造体の場合、String型のコード変換が行われないので、

行われる場合も確認していますし、行われない場合も確認しています。
確実に「行わせたくない」のであれば、構造体の定義及び関数の宣言には
タイプライブラリを用いる必要があるでしょう。

> >    // 文字列領域の作成にはSysAllocStringByteLenを用いること。
> SysAllocStringでないとだめなんではないでしょうか?(検証済み)
> # SysAllocStringByteLenはShift_JISコードの文字列領域を作成し、設定する。

配列を絡める場合は、部分的に変換される可能性が高い、といったところ
でしょう。

構造体内での文字列の保持に固定長文字列を使うのであれば、
VBではバイト型の配列として受けるべき、と考えるのは同意見です。

投稿時間:2006/04/15(Sat) 13:48
投稿者名:Blue
Eメール:
URL :
タイトル:
Re^2: VC++で作成したDLLにユーザ定義型の配列を指定したい
> 行われる場合も確認していますし、行われない場合も確認しています。
これは知りませんでした。
私の環境(WinXP SP2/VB6 SP6/VC2005,VC6 SP6)では行われないものしか確認できなかったのでつ
い、、、

あまりにも凝ったことやりたいならば、DLLでなくてCOM化したほうが良いってことですね。

投稿時間:2006/04/17(Mon) 09:32
投稿者名:morimori
Eメール:
URL :
タイトル:
Re: VC++で作成したDLLにユーザ定義型の配列を指定したい
Blue様、K.J.K様
返答をもらっておきながら返事が遅れてしまい、大変申し訳ございません。

> まず、仕様を整理する必要があるでしょう。
> 1,DLL側からVBに渡す構造体は、固定長の配列として渡されるのか?
>   それとも、そのときに応じて長さが変わる可変長の配列なのか?
> 2,DLL側からVBに渡す構造体の内部の文字列について、固定長なのか
>   可変長なのか?
DLL側では「char cReceiveData[100]」としているので、固定長の配列となります。

> > 結局DLLは何をするんでしょうか?WinSockって?
> > g_cReceiveDataはなに(文字列ですよね)?
> の説明があれば、SafeArrayのやつを考えようと思ったのですが、ないのでやりません。
「g_cReceiveData」は文字列を格納します。
今回、作成したDLLはソケット通信用のDLLでDLL側でソケット通信で受信した文字列を
VBに返すというプログラムを作成しております。
説明が遅れてしまい、申し訳ございません。

最終的には、K.J.K様のやり方でVBからは配列の先頭の要素を渡してやることで
成功することができました。
ご教授いただいて感謝しております。
今後とも、宜しくお願い致します。

以下にプログラムリストを載せておきます。

[VB側]
Private Type ReceiveInfo
    cReceiveData(0& To 99&) As Byte
End Type

Private Declare Function dllReceive Lib "dllWinSock.dll" (ByRef tReceiveData As ReceiveInfo) As Long

Private Sub Command3_Click()
    Dim tReceiveData(0 To 19&) As ReceiveInfo
    
    dllReadFindUid Text1.Text, tReceiveData(0&)

    '以下、省略
    
End Sub


[VC側]
extern "C" int WINAPI EXPORT dllReceive(ReceiveInfo *tReceiveInfo)
{
    long i;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    for(i=0;i<g_iReceiveCnt;i++){
        strcpy(tReceiveInfo[i].cReceiveData, g_cReceiveData[i]);
        strcpy(g_cReceiveData[i], "");
    }
    g_iReceiveCnt = 0;

    return 0;
}