none
データ型について RRS feed

  • 質問

  • こんにちは!
    今、VC6.0のプロジェクトで、VB6.0のDLLを利用する。
    渡すデータは文字列、戻り値は数字と想定していますが、
    具体的にデータ型を何に設定すればよいのでしょうか。

    現状では、下記のように設定しているんですが、
    VB DLL側:  Public Function DllFunc(ByVal mojiretu As String) As Integer
    VC側:      typedef int (__stdcall *DLLFUNC)(char*);
          DLLFUNC dllfunc;
                   ...
                  CHAR  Param[16];
                   int   iRet;
                   ...
                   iRet = dllfunc(Param);
                   ...
    VC側実行する時、Access Violationのエラーが出てくる。
    エラー詳細:"...例外処理 (初回) は *****.exe (OLEAUT32.DLL) にあります:

    0xC0000005: Access Violation。"
    それは多分渡す文字列引数の値が不正の原因と思いますが、詳細は分かりません。
    (debugモードでエラー発生するが、releaseモードでは発生しない)
    2007年2月19日 5:34

すべての返信

  • とりあえず、VB6のInteger型は2バイトですのでVC++のint型ではありません。

    >typedef int (__stdcall *DLLFUNC)(char*);
    typedef short (__stdcall *DLLFUNC)(char*);

    と、したらどうなりますか?

    # VB6でも、普通に(LoadLibrary + GetProcAddressで)VCで使える関数を作れるのか、、、
    # ActiveXDLLだからCOMとして扱わないとだめかと思っていた。orz
    2007年2月19日 6:36
  • ご回答、ありがとうございます。

    int→shortに変更したが、Access Violationのエラーがまだ未解決です。

    なお、文字列引数を取り消したら、正しく作動する。即ち、
    VB DLL側:  Public Function DllFunc() As Integer
    VC側:      typedef int (__stdcall *DLLFUNC)();
          DLLFUNC dllfunc;
               ...
               int   iRet;
               ...
               iRet = dllfunc();
               ...
    だから、問題は文字列引数であると思いますが、当引数をどう設定すればよいのか、分かりません。
    最初の例で、VB側は、Stringと定義していますが、VC側が渡したのはポインタである(配列の頭アドレス)。そこに問題があるかな。
    ①VC側が現状のまま(配列の頭アドレス)の場合,VB側のデータタイプは何にすればよいのか。
    ②VB側は現状のまま(As String), VC側はどのデータタイプを渡ればよいのか。

    PS: VBのDLL作成では、Linkerをカスタマズして、関数はちゃんとexportされた。普通のActiveX DLLではないので、COM使わなくでもよいと思います…
    2007年2月19日 9:08
  • VCからVBのDLLを使ったことないので微妙ですが、BSTRで指定するとどうでしょうか?
    BSTR文字列はSysAllocString関数を使ってください。
    それでだめならSysAllocStringByteLenでBSTRで渡す。


    追記:

    VC6(DLL)からVB6に文字列を返したことがありました。
    そのときは BSTR で SysAllocStringByteLen の値を返したらうまく動きました。
    http://hpcgi1.nifty.com/MADIA/Vcbbs/wwwlng.cgi?print+200606/06060060.txt

    BSTRは文字列の長さを持ちますので、char型配列の先頭ポインタでは、
    おそらくVB側で管理していない領域(4バイト分前の領域)を参照してしまい、
    エラーになっていたのでしょう。
    2007年2月19日 9:12
  • ご回答、ありがとうございます。

    ご提示頂いた例を参照したんですが、残念ながら、どうすればよいのかわかりませんでした。
    今VC側の物と言っても、実はWindowsAPIレベルのCです。

    下記のように試したんですが、Access Violationそのままです。
    VC側: typedef short (__stdcall *VBDLLFUNC)(BSTR *);
    VBDLLFUNC vbFunc;
    BSTR* bsParam;
    const char s[] = "hello";
    short sRet = 0;
    ...

    hdll = LoadLibrary("VBDLL");
    vbFunc = (VBDLLFUNC)GetProcAddress(hdll,"vbDllFunc");
    *bsParam=SysAllocStringByteLen( s, sizeof(s) - 1 );
    sRet = vbFunc(bsParam);
    ...
    VB側:  
    Public Function vbDllFunc(ByVal InStr As String) As Integer
    If InStr = "hello" Then
    vbDllFunc = 1
    Else
    vbDllFunc = 2
    End If
    End Function
    2007年2月19日 14:27
  • >VC側: typedef short (__stdcall *VBDLLFUNC)(BSTR *);
    typedef short (__stdcall *VBDLLFUNC)(BSTR);
    ではないかな?
    *をつけるのは、VB側で更新する場合、つまりByRefのときです。

    よってまとめると、

    typedef short (__stdcall *VBDLLFUNC)(BSTR);
    VBDLLFUNC vbFunc;
    BSTR bsParam;
    const char s[] = "hello";
    short sRet = 0;
    
    ・・・
    
    hdll = LoadLibrary("VBDLL");
    vbFunc = (VBDLLFUNC)GetProcAddress(hdll,"vbDllFunc");
    // sizeofを使っているけど配列数が固定でなければ // strlenを使ったほうがいいでしょう bsParam = SysAllocStringByteLen(s, sizeof(s) - 1); sRet = vbFunc(bsParam); // 使い終わったらBSTRの領域は解放する SysFreeString(bsParam);
    2007年2月19日 15:58
  • ちなみに、
    BSTR* bsParam;
    *bsParam=SysAllocStringByteLen( s, sizeof(s) - 1 );

    では、C言語レベルで大きな間違いがあります。
    bsParam は初期化されていない不定な値が入っているので、
    *bsParamとすると、不正なアクセスになってしまいます。

    (今回は違うけど)最終的に BSTR* の値を渡すのであれば

    BSTR bsParam;
    bsParam = SysAllocStringByteLen(s, sizeof(s) - 1);
    sRet = vbFunc(&bsParam);

    とすべきでしょう。
    (もしくは、malloc等で領域を確保する)
    2007年2月19日 23:38
  • ご回答、ありがとうございます。

    先の物は、確かに間違いました。

    なお、ご指摘通り見直したんですが、まだうまく行きません。
    2007年2月20日 2:49
  •  YoungerChinn さんからの引用
    ご回答、ありがとうございます。

    先の物は、確かに間違いました。

    なお、ご指摘通り見直したんですが、まだうまく行きません。
    VC側でデバッグできているのですよね?
    やはり、関数呼び出しでエラーになっていますか?

    ちなみに、LoadLibraryおよびGetProcAddress関数の戻り値は非0であるかのチェックは
    きちんと行っていますよね?

    当方でもDLLを用意できれば、実際に動かして確かめてみたいと思うのですが、
    VBでうまくDLLを作成できなかった(原因追求まではしていない)ので、、、
    # そもそもVBでレガシーDLLを作る必要性が私にはわからない。
    2007年2月20日 2:59
  • ご回答、ありがとうございます。

    ①エラー箇所は、「bsParam = SysAllocStringByteLen(s, sizeof(s) - 1);」です。
        LoadLibraryおよびGetProcAddressのところは、return値チェックし、
        step実行でも問題がなかったです。
       実は、引数は数字(As Integer)の場合,もう一個関数を作成してみました。
         うまく行きました。やはり、文字列引数の問題かなと思いました。

    ②VBでDLL作成する理由は、ActiveX利用とSoaptoolKit2.0利用のためです。
        VBなら、便利です(特にSoaptoolKit2.0のカスタマズmapperの作成まわり)。
        もし、文字列引数問題解決できなければ、VCで作成するしかないと思います。

    ③VBでレガシーDLL作成するには、VBのLinkerを書き換え、defをファイル作成すれば、
      普通のActiveX DLLと同じやり方で、レガシーDLL作成される。
      http://vb-helper.com/howto_make_standard_dll.htmlご参照
      詳細は、この中の
          http://www.windowsdevcenter.com/pub/a/windows/2005/04/26/create_dll.htmlご参照
    2007年2月20日 3:59
  • # 今手元にVB6がない&時間が取れさそうなのでVBでDLLを作って確かめるのは微妙かな。

    VB側が変更可能であれば、間際らしい?Stringで受けずに、Long型(char*型を受け取るため)と文字列の長さを渡して、
    CopyMemoryでByte型配列にコピーしてからStrConvで変換してString型に変換するようにするとか。

    Public Function vbDLLFunc(ByVal p As Long, ByVal n As Long) As Long
         ReDim str(n - 1) As Byte
         CopyMemory str(0), ByVal p, n
         If StrConv(str, vbUnicode) = "hello" Then
              vbDllFunc = 1
         Else
              vbDllFunc = 2
         End If
    End Function


    typedef int (__stdcall *VBDLLFUNC)(int, int);
    VBDLLFUNC vbFunc;
    const char s[] = "hello";
    int ret = 0;

    ret = vbFunc(s, strlen(s));

    2007年2月20日 4:23
  • 一応BSTRでやるならば、

    ・BSTR 型の引数
    ・BSTR* 型の引数

    ・SysAllocString
    ・SysAllocStringByteLen

    の組み合わせのパターンで検証してみてください。
    SysAllocStringの場合、wchar_t型ポインタを渡さないといけないので、必要に応じて
    char*型→wchar_t*型変換をしてください。
    (mbstowcsとかMultiByteToWideCharを使う。C++なら_bstr_tクラスを使うと便利なんだけど)
    2007年2月20日 4:28
  • ご回答、大変ありがとうございました。


    CopyMemoryのところ、コンパイルできないですが、「SubまたはFunctionが定義されていません」。 どこの設定上の問題かな。
    なお、lStrCpyでも可能ですか。


    2007年2月20日 4:42
  •  YoungerChinn さんからの引用
    ご回答、大変ありがとうございました。


    CopyMemoryのところ、コンパイルできないですが、「SubまたはFunctionが定義されていません」。 どこの設定上の問題かな。
    なお、lStrCpyでも可能ですか。


    WinAPI (のRtlMoveMemory)です。 Declare宣言してください。

    lstrcpy(lstrcpyA)でもできそうですが、終端文字を含むので、lstrcpyn(lstrcpynA)のほうが
    よさそうです。
    2007年2月20日 4:48
  •  蒼の洞窟 さんからの引用


    lstrcpy(lstrcpyA)でもできそうですが、終端文字を含むので、lstrcpyn(lstrcpynA)のほうが
    よさそうです。


    訂正

    lstrcpynではなんだかうまくいかない模様。

    いっそのことこうする。

    Private Declare Function lstrcpy Lib "Kernel32" Alias "lstrcpyA" _
        (ByVal lpString1 As String, ByVal lpString2 As Long) As Long

    Public Function vbDLLFunc(ByVal p As Long, ByVal n As Long) As Long
         Dim s As String
         s = String(n + 1, vbNullChar) ' n * 2 + 1 としておくほうが無難かも
         lstrcpy s, p
         If Left$(s, n) = "hello" Then
              vbDllFunc = 1
         Else
              vbDllFunc = 2
         End If
    End Function


    typedef int (__stdcall *VBDLLFUNC)(int, int);
    VBDLLFUNC vbFunc;
    const char s[] = "hello";
    int ret = 0;

    ret = vbFunc(s, _mbslen(s)); // バイト数ではなく文字数を渡す

    2007年2月20日 5:06
  • もう1つ案を思いついた。

    VARIANTでやり取りすれば、文字コード変換が絡まないから使えるかもしれない。

    Public Function vbDLLFunc(ByRef v As Variant) As Long
        If v = "hello" Then
            vbDLLFunc = 1
        Else
            vbDLLFunc = 2
        End If
    End Function
    typedef int (__stdcall *VBDLLFUNC)(VARIANT*)
    VBDLLFUNC vbFunc;
    const wchar_t s[] = L"hello";
    int ret = 0;
    VARIANT v;
    
    // vの初期化
    VariantInit(&v);
    // VARIANT型は文字列
    v.vt = VT_BSTR;
    // 文字列を格納
    v.bstrval = SysAllocString(s);
    
    // VBの関数に渡す
    ret = vbFunc(&v);
    
    // 文字列の解放
    SysFreeString(v.bstrVal);
    
    2007年2月20日 6:08
  • ご回答、ありがとうございます。

    案1と案2両方ともやってみます。多分引数参照出来ると思います。

    なお、突然ですが、VBで、ActiveXDLLを作成し、COMとして参照するとも
    考えていますが、レガシーDLLじゃないと、どう呼び出すのかわかりません。
    教えていただきたく思います。
    VB側:
    Public Function ReverseString(ByRef SomeString As String) As String
         ReverseString = StrReverse(SomeString)
    End Function
    VC側は、Win32APIレベルのCです。

    2007年2月20日 6:25
  • 試したこと。

    VBからVCで作成したDLLにAddressOf演算子を使って
    標準モジュールの関数ポインタを渡して、VCからその関数を使う。
    (VBの関数をVCから使うのでこれでも同じかなぁと。)
    一応これで成功しているようです。(よってSysAllocStringByteLenではなくSysAllocStringじゃないとだめっだったのかも)
    VARIANT*型のパターンも動くのを確認できました。
    ※VB6環境がないため、Excel2003のVBAで確認しています。

    typedef int (WINAPI *VBFUNC)(BSTR);
    
    void WINAPI VCDLL(VBFUNC f)
    {
        BSTR b;
        int n;
        char buff[256];
    
        b = SysAllocString(L"ABC日本語");
        n = f(b);
    
        sprintf(buff, "%d", n);
        MessageBox(NULL, buff, NULL, MB_OK);
    
        SysFreeString(b);
    }
    ' 標準モジュールに記述
    Private Declare Sub VCDLL Lib "MYDLL.dll" (ByVal f As Long)
    
    Public Function VBFunc(ByVal s As String) As Long
        MsgBox s
    End Function
    
    Sub hoge()
        VCDLL AddressOf VBFunc
    End Sub
    2007年2月20日 6:37
  • C言語からVB.NETのクラスライブラリを呼ぶ方法ですが、COMを使うということで
    ほぼ同じやり方でできます。
    http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=34448&forum=7

    ・CLSIDFromProgID
    ・CoCreateInstance
    ・GetIDsOfNames(IDispatch::lpVtblのメンバ関数(みたいなもの))
    ・Invoke(IDispatch::lpVtblのメンバ関数(みたいなもの))

    を使います。
    C++からであれば、もう少し簡単になります。

    正直ActiveXDLLは使わないほうがよさそうな。
    (使うのであれば、間際らしさを緩和するため全部ByRef As Variantにしたほうがよさそう。)
    2007年2月20日 6:51
  • 一応書いてみた。
    VARIANTにしてもしなくても、結局渡すときはVARIANTを介さないといけないので関係なかったです。
    #include <windows.h>
    
    HRESULT Test(LPCWSTR text)
    {
        HRESULT hr = S_OK;
        CLSID clsid;
        IDispatch* pDispatch = NULL;
    
        wchar_t* name;
        DISPID dispID;
    
        hr = CLSIDFromProgID(L"Project1.Class1", &clsid);
        if (FAILED(hr)) return hr;
    
        hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
                              &IID_IDispatch, (void**)&pDispatch);
        if (FAILED(hr)) return hr;
    
        name = L"ReverseString";
        hr = pDispatch->lpVtbl->GetIDsOfNames(pDispatch, &IID_NULL, &name, 1,
                                              LOCALE_USER_DEFAULT, &dispID);
        if (SUCCEEDED(hr))
        {
            VARIANT vtRet, vtParam;
            DISPPARAMS dp = {NULL, NULL, 0, 0};
            BSTR SomeString;
            
            SomeString = SysAllocString(text);
            VariantInit(&vtParam);
            vtParam.vt = VT_BSTR | VT_BYREF;
            vtParam.pbstrVal = &SomeString;
    
            dp.cArgs = 1;
            dp.rgvarg = &vtParam;
    
            VariantInit(&vtRet);
    
            hr = pDispatch->lpVtbl->Invoke(pDispatch, dispID, &IID_NULL, 
                LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, &vtRet, NULL, NULL);
            if (SUCCEEDED(hr))
            {
                if (vtRet.vt == VT_BSTR) 
                {
                    MessageBoxW(NULL, vtRet.bstrVal, L"結果", MB_OK);
                    SysFreeString(vtRet.bstrVal);
                }
                VariantClear(&vtRet);
            }
    
            SysFreeString(SomeString);
            VariantClear(&vtParam);
        }
        pDispatch->lpVtbl->Release(pDispatch);
    
        return hr;
    }
    
    int main(void)
    {
        if (SUCCEEDED(CoInitialize(NULL)))
        {
            Test(L"あいうえお");
            CoUninitialize();
        }
        return 0;
    }
    2007年2月20日 13:14
  • いろいろご回答、ありがとうございます。

    今のところで、VBで作成してActiveX DLLをVCから#includeして
    (Cファイルの拡張子は"cpp"に変更した)、
    文字列引数は、普通の配列→wchar→BSTRのルートで変換し、
    最終的に、VB DLLにBSTRを渡したら、うまく行きました。

    VBのレガシーDLLは、MSが保証しないの理由で諦められたが、
    個人的には、またレガシーの方(関数Export)が好きです。
    教えて頂いた二つの解決案も、これから試します。特にメモリアドレスと長さ
    を渡すの案は究極たと思い。
    今回いろいろ素早いご回答、本当に大変助かりました、
    ありがとうございます。

    PS:(蒼の洞窟)さんがいつでもonlineしているのような気がする :)

    2007年2月21日 7:44