VC6のDLLからVB6に文字列の配列を渡すには?

解決


にゃけ  2007-04-19 04:54:06  No: 64940

はじめまして。
VB6(SP6)のアプリに文字列の配列を渡すDLLをVC6 + WinXPで作ってます。
配列の要素数は状況によって変わるので、VBの動的配列に入れて渡すことしました。

下のようにコーディングしてみましたが、実行するとVB側には一部の文字が
「?」(クエスチョンマーク)に文字化けして表示されてしまいます。
これを文字化けしないように直したいのですが、どうしたらよいのでしょうか?
(化ける文字と化けない文字があるのは何故?)

下の例ですと、VBのListBox(List1)には

ABC
123456
あいうえお
?????
?うこそ
?しいy
今日の夕食はカ??にス??

と表示されてしまいます。「都」は「y」に化けます。

======== VBアプリ側 ========

Declare Function ListTest Lib "xxxxxx.dll" (ByRef list0() As String) As Long

Private Sub Command1_Click()
    Dim i As Integer
    Dim ref As Long
    Dim stlist() As String
    Dim st As String
    
    ref = ListTest(stlist())

    For i = 0 To UBound(stlist)
        st = StrConv(stlist(i), vbFromUnicode)
        List1.AddItem (st)
    Next i
End Sub

======== VC DLL側 ========
// _MBCSでコンパイルしてます

typedef std::basic_string<_TCHAR> STR; // _TCHAR string型

__declspec(dllexport) bool __stdcall ListTest(LPSAFEARRAY *ppsa)
{
  BSTR item0;
  long rglndices[1];

  std::vector<STR> itemlist;

  // 質問用にハードコーディングしてます
  itemlist.push_back(_T("ABC"));
  itemlist.push_back(_T("123456"));
  itemlist.push_back(_T("あいうえお"));
  itemlist.push_back(_T("らりるれろ"));
  itemlist.push_back(_T("ようこそ"));
  itemlist.push_back(_T("美しい都"));
  itemlist.push_back(_T("カレーにスルー"));

  // 既に領域が確保されている配列が渡されたら、その領域は破棄する
  if (*ppsa != NULL) {
    if (FAILED(::SafeArrayDestroy(*ppsa))) return false;
  }

  setlocale(LC_ALL, "Japanese");
  if (FAILED(::SafeArrayAllocDescriptor(1, ppsa))) return false;
  (*ppsa)->cbElements = sizeof(BSTR);
  (*ppsa)->fFeatures  = FADF_STATIC;
  (*ppsa)->rgsabound[0].lLbound = 0;
  (*ppsa)->rgsabound[0].cElements = itemlist.size();
  if (::SafeArrayAllocData((*ppsa)) != S_OK) return false;

  ::SafeArrayLock(*ppsa);
  setlocale(LC_ALL, "Japanese");
  for (int cnt = 0; cnt < itemlist.size(); cnt++) {
    rglndices[0] = cnt;
    _bstr_t bstr1(itemlist[cnt].c_str());
    item0 = bstr1.copy();
    ::SafeArrayPutElement(*ppsa, rglndices, &item0);
  }

  ::SafeArrayUnlock(*ppsa);
  return true;
}

よろしくお願いします。


にゃけ  2007-04-19 05:03:37  No: 64941

どうでもいいことですが。。
>今日の夕食はカ??にス??

カ??にス??
です。すみません。


Blue  2007-04-19 06:58:35  No: 64942

>st = StrConv(stlist(i), vbFromUnicode)
は不要では。stlist(i)はUnicodeで格納されているはずです。

というか、VB側では文字列はUnicodeでないといけないのにもかかわらず、
いちいちCP932(Shift_JIS)に変換しているようですけど。


にゃけ  2007-04-19 18:06:32  No: 64943

回答ありがとうございます。

>>st = StrConv(stlist(i), vbFromUnicode)
>は不要では。stlist(i)はUnicodeで格納されているはずです。
そう思って、

For i = 0 To UBound(stlist)
    List1.AddItem (stlist(i))
Next i

としてたのですが、これだとVB側の表示が

A
1
B0D0F0H0J0
・・・・・
・F0S0]0
・W0D0&#63729;
ォ0・・k0ケ0・・

となってしまうのです。

>というか、VB側では文字列はUnicodeでないといけないのにもかかわらず、
>いちいちCP932(Shift_JIS)に変換しているようですけど。
これはStrConv(stlist(i), vbFromUnicode)のことをおっしゃってるのですよね。
あと念のため、VC側のプリプロセッサの定義を_MBCSから_UNICODEに変えてみても同じ結果になりました。


にゃけ  2007-04-19 18:18:29  No: 64944

>・W0D0&#63729;
HTMLの実体参照に変換されちゃってますが、&#63729;(また変換されるとイヤなのでわざと全角にしました)の部分は実際は半角の黒い四角です。


Blue  2007-04-19 19:26:32  No: 64945

どうも文字列配列のばあい、CP932で配列に入れないとだめそうです。
よって

>item0 = bstr1.copy();

LPCSTR temp = bstr1;
item0 = ::SysAllocStringByteLen(temp, strlen(temp));


Blue  2007-04-19 20:38:53  No: 64946

1次元の文字型配列であればSafeArrayCreateVectorとSafeArrayAccessData
を使うほうがコードが簡単になります。

HRESULT WINAPI Sample(LPSAFEARRAY* ppsa)
{
    HRESULT hr = S_OK;

    if (!ppsa)  return E_INVALIDARG;    // 引数エラー

    if (!*ppsa)
    {
        // 既存配列の削除
        hr = ::SafeArrayDestroy(*ppsa);
        if (FAILED(hr)) return hr;
    }

    static const LPCSTR Foot_bola[3] = {"中村俊輔", "松井大輔", "高原直泰"};
    const long size = sizeof(Foot_bola) / sizeof(Foot_bola[0]);

/*
    // 配列の再確保
    hr = ::SafeArrayAllocDescriptor(1,  ppsa);
    if (FAILED(hr)) return hr;

    // 要素のサイズを設定
    (*ppsa)->cbElements = sizeof(BSTR);
    (*ppsa)->fFeatures  = FADF_STATIC;

    // 配列のサイズを設定
    (*ppsa)->rgsabound[0].lLbound   = 0;
    (*ppsa)->rgsabound[0].cElements = size;

    // 格納領域の確保
    hr = ::SafeArrayAllocData(*ppsa);
    if (FAILED(hr))
    {
        ::SafeArrayDestroyDescriptor((*ppsa));
        return hr;
    }
    // 要素の設定
    ::SafeArrayLock(*ppsa);
    
    BSTR item;

    for (long index = 0; index < size; ++index)
    {
        ::SafeArrayGetElement(*ppsa, &index, &item);
        
        ::SysFreeString(item);
        // 文字列配列の場合マルチバイト文字列として格納する
        item = ::SysAllocStringByteLen(Foot_bola[index], strlen(Foot_bola[index]));

        ::SafeArrayPutElement(*ppsa, &index, &item);
    }
    ::SafeArrayUnlock(*ppsa);
*/
    *ppsa = ::SafeArrayCreateVector(VT_BSTR, 0L, size);
    if (!*ppsa) return E_OUTOFMEMORY; // メモリ不足     

    // 要素の設定
    BSTR* items;
    ::SafeArrayAccessData(*ppsa, (LPVOID*)&items);
    for (long index = 0; index < size; ++index)
    {
        ::SysFreeString(items[index]);
        // 文字列配列の場合マルチバイト文字列として格納する
        items[index] = ::SysAllocStringByteLen(Foot_bola[index], strlen(Foot_bola[index]));
    }
    ::SafeArrayUnaccessData(*ppsa);

    return hr;
}

それとこのコードは、からの配列を渡す分にはきちんと格納されるようですけど、
配列が既にある(*ppsa!=NULL)パターンのときちゃんと格納できないような
気がします。


にゃけ  2007-04-19 21:35:26  No: 64947

>どうも文字列配列のばあい、CP932で配列に入れないとだめそうです。
うわ〜ん。ややこしいですね。。

>LPCSTR temp = bstr1;
>item0 = ::SysAllocStringByteLen(temp, strlen(temp));

これで意図通りに動くようになりました。
けど、なんでしょうか、自分で作った小包を自分でほどいてるような、そんな空しさがありますね。。
教えてもらっておきながらすみません。

ともかくありがとうございました!!


にゃけ  2007-04-19 21:41:53  No: 64948

あっ、2007/04/19(木) 11:38:53の書き込みがあるのに気づかずに送ってしまいました。
2007/04/19(木) 11:38:53の記事について今から検討します。すみません


Blue  2007-04-19 21:55:21  No: 64949

>if (!*ppsa)
条件式が間違っていた。

if (*ppsa)

です。


Blue  2007-04-19 22:16:52  No: 64950

ちなみにVARIANT型配列で受け渡しする場合はもっと簡単になります。

HRESULT WINAPI Sample(LPSAFEARRAY* ppsa)
{
    HRESULT hr = S_OK;

    if (!ppsa)  return E_INVALIDARG;    // 引数エラー

    if (*ppsa)
    {
        // 既存配列の削除
        hr = ::SafeArrayDestroy(*ppsa);
        if (FAILED(hr)) return hr;
    }

    static const LPCTSTR Foot_bola[3] = {_T("中村俊輔"), _T("松井大輔"), _T("高原直泰")};
    const long size = sizeof(Foot_bola) / sizeof(Foot_bola[0]);

    *ppsa = ::SafeArrayCreateVector(VT_VARIANT, 0L, size);
    if (!*ppsa) return E_OUTOFMEMORY; // メモリ不足     

    // 要素の設定
    VARIANT* items;
    ::SafeArrayAccessData(*ppsa, (LPVOID*)&items);
    for (long index = 0; index < size; ++index)
    {
        ::VariantClear(&items[index]);
        _variant_t temp(Foot_bola[index]);
        items[index] = temp.Detach();
    }
    ::SafeArrayUnaccessData(*ppsa);

    return hr;
}


にゃけ  2007-04-19 23:24:43  No: 64951

ありがとうございます。

>それとこのコードは、からの配列を渡す分にはきちんと格納されるようですけど、
>配列が既にある(*ppsa!=NULL)パターンのときちゃんと格納できないような
>気がします。

ええっと、これは

>条件式が間違っていた。
>
>if (*ppsa)

で解決でしょうか?
あらかじめ配列に何か入れておいてから、上の関数(条件式を直した物)を
呼び出しても見かけ上、異常動作しているようには見えないのですが。。

>1次元の文字型配列であればSafeArrayCreateVectorとSafeArrayAccessData

確かにこの方がすっきりしてますね。

>ちなみにVARIANT型配列で受け渡しする場合はもっと簡単になります。

VARIANT型の扱いって、なんか難しそうでいままでずっと敬遠していました。
_variant_tなんてラッパークラスもあるんですね。


Blue  2007-04-19 23:40:09  No: 64952

>で解決でしょうか?
実行してみるとわかると思いますが、解決ではないです。
単にロジックが違っていたから変えただけです。

Stringの場合

VB→CのDLL
VB←CのDLL

のときに暗黙的に文字コードが変わるようです。
(変わらない環境もあるとかないとか。詳しくないです)

逆にVariantの場合は暗黙的に文字コードが変わらないので
こちらをつかったほうがよいかもしれませんね。

(でもWinAPIをVBから使う場合 LPCSTR の引数には普通に ByVal XXX As String
になっているんですよねぇ。。
VB.NETのようにマーシャラに文字コードを教えるみたいなことはできないし。)


にゃけ  2007-04-20 03:59:31  No: 64953

>>で解決でしょうか?
>実行してみるとわかると思いますが、解決ではないです。

う〜ん。。
VB側で

Dim Foot_bola_A() As String

としておいて後のほうで

ReDim Foot_bola_A(2)
Foot_bola_A(0) = "稲本潤一"
Foot_bola_A(1) = "中田浩二"
Foot_bola_A(2) = "森本貴幸"
Sample(Foot_bola_A())

とかやると期待通り動いてるように見えるんですが
代わりに

Dim Foot_bola_B(2) As String

Foot_bola_B(0) = "小笠原満男"
Foot_bola_B(1) = "宮本恒靖"
Foot_bola_B(2) = "中田英寿"
Sample(Foot_bola_B())

普通の配列、動的配列に対して静的配列っていうんですか?
これで試すとうまく格納できませんねぇ。。
でも、「動的配列で」という前提なんでこれは仕様の範囲内
じゃないかと思ってます。

>逆にVariantの場合は暗黙的に文字コードが変わらないので
>こちらをつかったほうがよいかもしれませんね。

そうなんですか。VARIANT型を使うように変更しようかな。
ありがとうございました。

とりあえず、お昼にググってたらこんな文書を見つけたので
これを読んで勉強します。
http://www.microsoft.com/japan/msdn/vs_previous/vbasic/docs/dll/


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

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






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