问题
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?
回答1:
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.)
回答2:
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;
}
来源:https://stackoverflow.com/questions/1796118/how-to-build-a-safearray-of-pointers-to-variants