dllで文字列配列を戻し、取得するには

解決


matsu  2006-11-24 03:50:04  No: 63675

OS:WinXPPro SP2
VC++.NET

dllで文字列配列を戻し、他のexeで受取る方法が
分からなくて困っています。
dllでsplit関数を作成し、使用したいのですが...

(dll側)
extern "C" __declspec(dllexport) SAFEARRAY* __stdcall getSplitText(LPCTSTR pszTarget, LPCTSTR pszDelimiter);
extern "C" __declspec(dllexport) SAFEARRAY* __stdcall getSplitText(LPCTSTR pszTarget, LPCTSTR pszDelimiter)
{
///// 配列を用意
  CStringArray* arrResult = new CStringArray();
  int nDelimiter;
  int nStart;
  int nEnd;
  CString strTarget = pszTarget;
  CString strDelimiter = pszDelimiter;

  strTarget = strTarget + strDelimiter;
  nDelimiter = strDelimiter.GetLength();
  nStart = 0;
  while(nEnd = strTarget.Find(strDelimiter, nStart), nEnd >= 0)
  {
    arrResult->Add(strTarget.Mid(nStart, nEnd - nStart));
    nStart = nEnd + nDelimiter;
  }

  SAFEARRAY* pSa;
  SAFEARRAYBOUND bound;
  bound.cElements = (ULONG)arrResult->GetSize();
  bound.lLbound = 0;
  pSa = SafeArrayCreate(VT_BSTR, 1, &bound);
  // SAFEARRAYにアクセスする.
  LPCTSTR* sArray;
  SafeArrayAccessData(pSa, (void**)&sArray);

  for(int i = 0; i < arrResult->GetSize(); i++)
  {
    sArray[i] = arrResult->operator [](i);
  }
  SafeArrayUnaccessData(pSa);

  delete arrResult;

  return pSa;
}

(exe側)
typedef SAFEARRAY* (__stdcall *DISPLAYMESSAGE)(LPCTSTR, LPCTSTR);

void CTest2View::OnBnClickedButton2()
{
  HINSTANCE hDllInstance;
  DISPLAYMESSAGE Split;  // 関数アドレス

  hDllInstance = ::LoadLibrary("D:\\xxx\\yyy\\ComProc.dll");

  if( hDllInstance != NULL )
  {
    // エクスポートされた関数のアドレスを取得
    Split = (DISPLAYMESSAGE)(::GetProcAddress(hDllInstance, "getSplitText"));
    if( Split != NULL )
    {
      CStringArray* arrResult = new CStringArray();
      SAFEARRAY* pSa;
      pSa = Dll_TestMessageBox("123,456,789", ",");

      // SAFEARRAYにアクセスする.
      LPCTSTR* sArray;
      SafeArrayAccessData(pSa, (void**)&sArray);

      for(int i = 0; i < 3; i++)
      {
         arrResult->Add(sArray[i]);
      }
      SafeArrayUnaccessData(pSa);

      AfxMessageBox(arrResult->operator [](0));
      delete arrResult;
    }
  }

  FreeLibrary(hDllInstance);
}

だとアクセス違反になります。
SAFEARRAYからの取り出し方に問題があるのでしょうか?
よろしくお願いします。


Blue  2006-11-24 06:02:17  No: 63676

>sArray[i] = arrResult->operator [](i);
文字列領域を確保していないのでは?

>LPCTSTR* sArray;
BSTR* sArray;
なんじゃないかな。
んで、
格納するときは、SysAllocStringかSysAllocStringLengthをつかうのかな。


Blue  2006-11-24 08:08:31  No: 63677

>格納するときは、SysAllocStringかSysAllocStringLengthをつかうのかな。
CString の AllocSysString を使うほうがべんりですな。

サンプル)
// DLL側
// 本来ならば、HRESULTで成功したかどうかを返し、引数に LPSAFEARRAY* を指定して格納するほうがよい
SAFEARRAY* WINAPI Sample21()
{
    SAFEARRAYBOUND sb;
    sb.lLbound   = 0;
    sb.cElements = 3;
    SAFEARRAY* psa = ::SafeArrayCreate( VT_BSTR, 1, &sb );
    
    BSTR* data;
    ::SafeArrayAccessData( psa, ( void** )&data );
    
    data[ 0 ] = ::SysAllocString( L"イチロー" );
    data[ 1 ] = ::SysAllocString( L"松井秀喜" );
    data[ 2 ] = ::SysAllocString( L"松坂大輔" );

    ::SafeArrayUnaccessData( psa );

    return psa;
}

// exe側
#include <windows.h>

int main()
{
    HMODULE hDLL = ::LoadLibrary( TEXT( "C:\\XXX\\YYY\\hoge.dll" ) );
    if ( hDLL )
    {
        SAFEARRAY* ( WINAPI *func )();
        func = ( SAFEARRAY* ( WINAPI * )() )::GetProcAddress( hDLL, TEXT( "Sample21" ) );

        SAFEARRAY* psa = ( *func )();
        long lb, ub;
        BSTR data;
        ::SafeArrayGetLBound( psa, 1, &lb );
        ::SafeArrayGetUBound( psa, 1, &ub );
        for ( long i = lb; i <= ub; i++ )
        {
            ::SafeArrayGetElement( psa, &i, &data );
            ::MessageBoxW( NULL, data, L"てすと", MB_OK );
            ::SysFreeString( data );
        }

        // 後処理
        // DLL側で処理させる関数を作るべき
        for ( long j = lb; j <= ub; j++ )
        {
            ::SafeArrayGetElement( psa, &j, &data );
            ::SysFreeString( data );
        }
        ::SafeArrayDestroy( psa );

        ::FreeLibrary( hDLL );
    }
    return 0;
}

ちなみに、C++⇔C++であればSafeArrayを使わなくてもいいような気もする。
→APIでよくある、\0で配列を区切り、終端を\0\0にしたり。
(MFCのDLLを使えば、CStringArrayを普通に使えたような。)


matsu  2006-11-24 08:26:10  No: 63678

Blueさんいつもありがとうございます。

(dll側)
BSTR* sArray;
SafeArrayAccessData(pSa, (void**)&sArray);

BSTR bstr = SysAllocString(L"");
BSTR bs;
for(int i = 0; i < arrResult->GetSize(); i++)
{
  bs = arrResult->operator [](i).SetSysString(&bstr);
  sArray[i] = bs;
}

(exe側)
#include <AfxPriv2.h>

BSTR* sArray;
SafeArrayAccessData(pSa, (void**)&sArray);

CString Str;
for(int i = 0; i < 3; i++)
{
  AfxBSTR2CString(&Str, sArray[i]);
  arrResult->Add(Str);
}

エラーは出なくなり、メッセージボックスは表示されますが
789しか表示されません。
取出し方に問題があるのでしょうか?
よろしくお願いします。


matsu  2006-11-24 08:44:47  No: 63679

すみません、Blueさんの次の書込みを見ていませんでした。

(exe側)
      SAFEARRAY* psa = Dll_TestMessageBox("123,456,789", ",");
      long lb, ub;
      BSTR data;
      ::SafeArrayGetLBound( psa, 1, &lb );
      ::SafeArrayGetUBound( psa, 1, &ub );
      for ( long i = lb; i <= ub; i++ )
      {
        ::SafeArrayGetElement( psa, &i, &data );
        ::MessageBoxW( NULL, data, L"てすと", MB_OK );
        ::SysFreeString( data );
      }

      for ( long j = lb; j <= ub; j++ )
      {
        ::SafeArrayGetElement( psa, &j, &data );
        ::SysFreeString( data );
      }
      ::SafeArrayDestroy( psa );
でも、789しか表示されません。


Blue  2006-11-24 09:25:55  No: 63680

>bs = arrResult->operator [](i).SetSysString(&bstr);
ちがうっぽ。

//BSTR bstr = SysAllocString(L"");
//BSTR bs;
for(int i = 0; i < arrResult->GetSize(); i++)
{
//bs = arrResult->operator [](i).SetSysString(&bstr);
sArray[i] = arrResult->GetAt(i).AllocSysString();
}

それと気になったこと
>CStringArray* arrResult = new CStringArray();
なんで new するのでしょうか?
単に
CStringArray arrResult;
で十分でしょう。

それと、ソースコードをのせるときは、タブを半角空白に変換してから載せてください。
インデントが消えると非常に読みにくいですので。


matsu  2006-11-24 09:40:50  No: 63681

Blueさん、いつもいつもありがとうございます。

SAFEARRAYを使用したのはVBでも使いたかったからです。
VBのテストもOKでした。

ソースの載せ方、気をつけます。


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

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






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