How to build a SAFEARRAY of pointers to VARIANTs?
Asked Answered
M

2

7

I'm trying to use a COM component with the following method:

HRESULT _stdcall Run(
    [in] SAFEARRAY(BSTR) paramNames,
    [in] SAFEARRAY(VARIANT *) paramValues
    );

How can I create in C/C++ the paramValues array?

Mercie answered 25/11, 2009 at 10:48 Comment(0)
C
8

Adding to the answers above for reference by future readers: In IDL, SAFEARRAY(...) means a pointer to an array descriptor. But in C++, SAFEARRAY means an array descriptor. So IDL's SAFEARRAY(...) is really C++'s SAFEARRAY *. This confused me to no end. To make things even more interesting, VB always passes arrays by reference. So VB's () As Long is SAFEARRAY<int32_t> ** in C++. (I don't know if there actually is a commonly used header that allows you to specify the type as a template parameter, but I inserted it for clarity.)

Chloris answered 3/6, 2011 at 13:4 Comment(1)
+1 for comment on differences between SAFEARRAY in IDL context and the SAFEARRAY C/C++ structTricky
T
7

The definition SAFEARRAY(VARIANT *) is not quite correct. It is declared in an IDL as SAFEARRAY(VARIANT), but the pointer available from locking the SAFEARRAY is actually a VARIANT *. If you think about this for a moment, it should make some more sense. The index pointer of a SAFEARRAY (the pvData member) can't possibly fit an entire VARIANT in its physical location, so at the very least, it should be able to store a pointer that may be used to index into an array of VARIANTs.

If you look at <wtypes.h>, somewhere about line 1110+ you'll see the VT_ enumeration definitions. It is also shown there that VT_VARIANT actually implies VARIANT *. Also handy are the [S] tags noting what items may appear in a SAFEARRAY.

/*
 * VARENUM usage key,
 *
 * * [V] - may appear in a VARIANT
 * * [T] - may appear in a TYPEDESC
 * * [P] - may appear in an OLE property set
 * * [S] - may appear in a Safe Array
 *
 *
 *  VT_EMPTY            [V]   [P]     nothing
 *  VT_NULL             [V]   [P]     SQL style Null
 *  VT_I2               [V][T][P][S]  2 byte signed int
 *  VT_I4               [V][T][P][S]  4 byte signed int
 *  VT_R4               [V][T][P][S]  4 byte real
 *  VT_R8               [V][T][P][S]  8 byte real
 *  VT_CY               [V][T][P][S]  currency
 *  VT_DATE             [V][T][P][S]  date
 *  VT_BSTR             [V][T][P][S]  OLE Automation string
 *  VT_DISPATCH         [V][T]   [S]  IDispatch *
 *  VT_ERROR            [V][T][P][S]  SCODE
 *  VT_BOOL             [V][T][P][S]  True=-1, False=0
 *  VT_VARIANT          [V][T][P][S]  VARIANT *
 ... (remaining definitions omittted)
 */

Here's a link to a copy of the header file.

wtypes.h at DOC.DDART.NET

Proceeding from here, you would simply declare a SAFEARRAY with a variant type of VT_VARIANT, then treat pvData as VARIANT * when locking the array. Here is the source code for a sample win32 console app that demonstrates this by calling a function matching the same declaration as your function.

#include "stdafx.h"
#include "SFAComponent.h"
#include "SFAComponent_i.c"

int _tmain(int argc, _TCHAR* argv[])
{
  ::CoInitialize(NULL);

  SAFEARRAYBOUND nameBounds;
  nameBounds.cElements = 2;
  nameBounds.lLbound = 0;
  LPSAFEARRAY psaNames = SafeArrayCreate(VT_BSTR, 1, &nameBounds);

  BSTR bstrApple = SysAllocString(L"apple");
  BSTR bstrOrange = SysAllocString(L"orange");

  SafeArrayLock(psaNames);
  BSTR *nameArray = (BSTR *)psaNames->pvData;
  nameArray[0] = bstrApple;
  nameArray[1] = bstrOrange;
  SafeArrayUnlock(psaNames);

  SAFEARRAYBOUND valueBounds;
  valueBounds.cElements = 2;
  valueBounds.lLbound = 0;
  LPSAFEARRAY psaValues = SafeArrayCreate(VT_VARIANT, 1, &valueBounds);

  SafeArrayLock(psaValues);
  VARIANT *valueArray = (VARIANT *)psaValues->pvData;
  VariantClear(&valueArray[0]);
  VariantClear(&valueArray[1]);
  valueArray[0].vt = VT_BSTR;
  valueArray[0].bstrVal = SysAllocString(L"hello");
  valueArray[1].vt = VT_I4;
  valueArray[1].iVal = 42;

  {
    CComPtr<ITestReader> p;
    p.CoCreateInstance(CLSID_TestReader);
    p->Run(psaNames, psaValues);
    p.Release(); // not explicitly necessary.
  }

  SafeArrayDestroy(psaValues);
  SafeArrayDestroy(psaNames);

  ::CoUninitialize();

  return 0;
}

The component called by this test app can be created by creating an ATL dll project, and adding a simple ATL object called 'TestReader'.

Here's the IDL for ITestReader.

[
  object,
  uuid(832EF93A-18E8-4655-84CA-0BA847B52B77),
  dual,
  nonextensible,
  helpstring("ITestReader Interface"),
  pointer_default(unique),
  oleautomation
]
interface ITestReader : IDispatch{
  [id(1), helpstring("method Run")] HRESULT Run([in] SAFEARRAY(BSTR) paramNames, [in] SAFEARRAY(VARIANT) paramValues);
};

The member function corresponding to the IDL declaration just takes SAFEARRAY * (or LPSAFEARRAY) arguments.

public:
  STDMETHOD(Run)(LPSAFEARRAY paramNames, LPSAFEARRAY paramValues);

Here is the body of the method. Also included is a helper function PrintVariant() for brevity.

void PrintVariant(VARIANT *pV)
{
  switch(pV->vt)
  {
  case VT_BSTR:
    wprintf(L"  BSTR: %s\r\n", pV->bstrVal);
    break;
  case VT_I4:
    wprintf(L"  Integer: %d\r\n", pV->iVal);
    break;
  default:
    wprintf(L"  Unrecognized Type: vt=%d\r\n", pV->vt);
    break;
  }
}

STDMETHODIMP CTestReader::Run(LPSAFEARRAY paramNames, LPSAFEARRAY paramValues)
{
  SafeArrayLock(paramNames);
  SafeArrayLock(paramValues);
  BSTR *nameArray = (BSTR *)paramNames->pvData;
  VARIANT *valueArray = (VARIANT *)paramValues->pvData;

  wprintf(L"Item 0 is %s, variant type %d\r\n", nameArray[0], valueArray[0].vt);
  PrintVariant(&valueArray[0]);
  wprintf(L"Item 1 is %s, variant type %d\r\n", nameArray[1], valueArray[1].vt);
  PrintVariant(&valueArray[1]);

  SafeArrayUnlock(paramNames);
  SafeArrayUnlock(paramValues);

  return S_OK;
}
Tricky answered 9/12, 2009 at 6:29 Comment(1)
How can i pass array of strings from VB script to SAFEARRAY method ? can you please post an example ?Braxy

© 2022 - 2024 — McMap. All rights reserved.