Sending and receiving arrays over COM - c++

What is the right way to receive and send arrays over COM? Here's my attempt so far: a safearray of doubles wrapped in a variant.
//takes variant holding safearray of doubles
//returns a similar variant having multipled every element by 2
STDMETHODIMP MyComClass::safearraytimestwo(VARIANT in, VARIANT* out)
{
CComSafeArray<double> sa_in;
sa_in.Attach(*in.pparray);
ULONG size = sa_in.GetCount();
CComSafeArray<double> *out_sa = new CComSafeArray<double>(size);
for (long i=0;i<size;i++)
out_sa->SetAt(i,sa_in[i]*2);
out = new CComVariant(out_sa);
return S_OK;
}
Problems:
- currently compilation fails on the loop operation: error C2679: binary '=' : no operator found which takes a right-hand operand of type 'ATL::_ATL_AutomationType<DOUBLE>::_typewrapper' (or there is no acceptable conversion) edit: solved using SetAt() instead of operator[]
- Should I be declaring out_sa on the heap? Will it get deallocated when out gets deallocated (which I can only presume the client will do?)
Any help would be greatly appreciated!
Edit 2: here is a partial implementation that tries just to return a safearray.
STDMETHODIMP CSpatialNet::array3(VARIANT in, VARIANT* out)
{
CComSafeArray<double> out_sa;
out_sa.Create(2);
out_sa.SetAt(0,1.2);
out_sa.SetAt(1,3.4);
*out = CComVariant(out_sa);
out_sa.Detach();
return S_OK;
}
This also fails; lisp reports
(vl-load-com)
(setq n (vlax-create-object "sdnacomwrapper.SpatialNet"))
(setq v (vlax-make-variant 1.0))
(vlax-invoke-method n 'array3 v 'newvar)
; error: ActiveX Server returned an error: The parameter is incorrect
Replacing CComSafeArray<double> with an array of variants produces the same error.

Got this working - my code is this (edit: though apparently not without faults - see Dietrich's answer):
STDMETHODIMP MyComClass::arraytimestwo(VARIANT in, VARIANT* out)
{
CComSafeArray<double> sa_in;
sa_in.Attach(in.parray);
ULONG size = sa_in.GetCount();
CComSafeArray<double> out_sa;
out_sa.Create(size);
for (long i=0;i<size;i++)
out_sa.SetAt(i,sa_in.GetAt(i)*2);
CComVariant(out_sa).Detach(out);
return S_OK;
}
And in Lisp...
(vl-load-com)
(setq n (vlax-create-object "mycomdll.MyComClass"))
(setq sa (vlax-make-safearray vlax-vbDouble '(0 . 1)))
(vlax-safearray-fill sa '(1 2))
(vlax-safearray->list sa)
(vlax-invoke-method n 'arraytimestwo sa 'newvar)
(vlax-safearray->list newvar)
Things specifically wrong with the original attempts:
needed to use Detach method to assign value to out
needed to attach to in.parray not *in.pparray (not the same thing)

A COM method taking VARIANT parameters is responsible for checking arguments, for catching exceptions and it is not going to actually destroy [in] array, so a more accurate implementation on C++ side would be:
STDMETHODIMP Foo(VARIANT in, VARIANT* out)
{
_ATLTRY
{
ATLENSURE_THROW(in.vt == (VT_ARRAY | VT_R8), E_INVALIDARG);
ATLENSURE_THROW(out, E_POINTER);
VariantInit(out);
CComSafeArray<DOUBLE>& sa_in =
reinterpret_cast<CComSafeArray<DOUBLE>&>(in.parray);
ULONG size = sa_in.GetCount();
CComSafeArray<DOUBLE> out_sa;
ATLENSURE_SUCCEEDED(out_sa.Create(size));
for(ULONG nIndex = 0; nIndex < size; nIndex++)
out_sa.SetAt(nIndex, sa_in.GetAt(nIndex) * 2);
// NOTE: Constructor copies data so it's accurate just inefficient
ATLVERIFY(SUCCEEDED(CComVariant(out_sa).Detach(out)));
}
_ATLCATCH(Exception)
{
return Exception;
}
return S_OK;
}

The solutions of Sideshow Bob and Roman R. use
ComVariant(out_sa).Detach(out);
This has a serious drawback. The SAFEARRAY out_sa is passed to the CComVariant's constructor and the constructor will make a copy of the SAFEARRAY. To avoid a copy better use
::VariantInit(out);
out->vt = (VT_ARRAY | VT_R8);
out->parray = out_sa.Detach();
As Roman pointed out, you should also start with checking whether in really is of type VT_ARRAY | VT_R8. Bob's solution has imho a serious fault: in.parray is attached to sa_in but not detached and thus the destructor will destroy in.parray. But by the rules of COM, the function arraytimestwo(VARIANT in,...is not allowed to modify the argument in. COM is full of traps. Therefore, I think it's better to pass the parameter in by reference.
I give a (hopefully!) improved solution and a test function:
STDMETHODIMP arraytimestwo(const VARIANT &in, VARIANT* out)
{
try
{
if (in.vt != (VT_ARRAY | VT_R8)) return E_INVALIDARG;
CComSafeArray<double> sa_out;
variant_t wrapCopyIn(in);
sa_out.Attach(wrapCopyIn.parray);
if (sa_out.GetDimensions() > 1) return E_INVALIDARG;
for (long i = sa_out.GetLowerBound(0); i <= sa_out.GetUpperBound(0); i++)
sa_out[i] *= 2;
//Don't forget
sa_out.Detach();
*out = wrapCopyIn.Detach();
}
catch (const CAtlException& e)
{
// Exception object implicitly converted to HRESULT,
// and returned as an error code to the caller
return e;
}
return S_OK;
}
void TestArraytimestwo()
{
CComSafeArray<double> vec(5, 1);
for (int i = vec.GetLowerBound(); i <= vec.GetUpperBound(); i++) vec[i] = i * 1.1;
variant_t in, out;
in.vt = (VT_ARRAY | VT_R8);
in.parray = vec.Detach();
if (!SUCCEEDED(arraytimestwo(in, &out)))
{
std::cout << "Something went wrong!" << "\n";
return;
}
CComSafeArray<double> sa_out;
sa_out.Attach(out.parray);
vec.Attach(in.parray);
for (int i = vec.GetLowerBound(); i <= vec.GetUpperBound(); i++)
std::cout << vec[i] << " " << sa_out[i] << std::endl;
//Not necessary, but I do it out of habit
vec.Detach();
sa_out.Detach();
}
Remark: Bob's original function should be like this (skipping try ... catch)
STDMETHODIMP arraytimestwoBob(const VARIANT &in, VARIANT* out)
{
CComSafeArray<double> sa_in;
sa_in.Attach(in.parray);
CComSafeArray<double> out_sa(sa_in.GetCount(), sa_in.GetLowerBound());
for (long i = sa_in.GetLowerBound(); i <= sa_in.GetUpperBound(); i++) out_sa[i] = 2 * sa_in[i];
sa_in.Detach();//Detach, this function doesn't own in
::VariantInit(out);
out->vt = (VT_ARRAY | VT_R8);
out->parray = out_sa.Detach();
return S_OK;
}

Related

Should I delete vector<short>?

I have the following code to pass the values of a float vector. The vector holds audio data.
However, the receiving object expects a temporary vector, so I convert the floats to shorts, then pass this vector along:
HRESULT CApp::PassAudio(ISpTTSEngineSite * pOutputSite, const SPVTEXTFRAG * uFragList, vector<float>&uFloats,int &uSamplesWritten)
{
vector<short>nShortsFromFloats;
nShortsFromFloats.resize(uFloats.size());
for (int i = 0; i < (int)uFloats.size(); i++)
{
nShortsFromFloats[i] = (int)(uFloats[i] * 32767);
}
//Pass the audio (shorts now!) back to the requesting site
ULONG iSizeShorts = nShortsFromFloats.size();
uSamplesWritten += (iSizeShorts * 2);
HRESULT hr;
hr = pOutputSite->Write((short *)&nShortsFromFloats[0], (iSizeShorts * 2), NULL);
if (hr == S_OK)
{
//fine :-)
}
else
{
//some warning
}
return hr;
}
I would like to know if I need to delete the vector afterwards or if this is done automatically.
I'm not sure which other info is needed. I'm using this code in a DLL.
Thank you.
Nothing is being allocated with new, so there is nothing to delete. The vector will be destructed automatically when it goes out of scope when the function exits.

How to pass string array from VBScript to COM+

I have written a C++ COM which is running as COM+ application.
I am trying to access COM functionality from VBScript (ASP application).
I am able to call a function of COM from VBScript which takes a string. But when I try to call a COM function which takes an array of string, I could get length of array but I could not retrieve elements from that array at COM side.
VBScript (ASP application)
dim myComObj
Set myComObj = Server.CreateObject("ProgId_PerlCOMSimple.1")
Dim myArray(3)
myArray(0) = "Clean Underwear"
myArray(1) = "Vacuum Cleaner"
myArray(2) = "New Computer"
myArray(3) = "Talking Bass"
strDfStatus = myComObj.TestArray1 (myArray)
C++ COM which runs as COM+ application (through dllHost.exe)
STDMETHODIMP CPerlCOMSimple::TestArray1(VARIANT* testArray, LONG* lResult)
{
// TODO: Add your implementation code here
*lResult = testArray->parray->rgsabound->cElements;
BSTR** StrPtr = 0;
//LONG* pVals;
long LowerBound = 0;
long UpperBound = 0;
int i;
SafeArrayGetLBound(testArray->parray, 1, &LowerBound);
SafeArrayGetUBound(testArray->parray, 1, &UpperBound);
SafeArrayAccessData(testArray->parray, (void**)&pVals);
for (i = LowerBound; i <= UpperBound; ++i)
{
BSTR* lVal = StrPtr[i];
lVal++;
}
SafeArrayUnaccessData(testArray->parray);
return S_OK;
}
VBScript will not generate a SAFEARRAY with vartype VT_BSTR, which is what you are expecting. It will have VARTYPE VT_VARIANT.
// check all your parameters
if(testarray == null) return E_INVALIDARG;
if(testarray->vartype != VT_ARRAY|VT_BSTR
&& testarray->vartype != VT_ARRAY|VT_VARIANT)
return E_INVALIDARG;
if(testarray->parray == null) return E_INVALIDARG;
// Now we have established we have an array, and that it
// is either a string array or a variant array.
VARTYPE vt = VT_EMPTY;
SafeArrayGetVarType(testarray->pArray, &vt);
// Now we need to take different actions based on the vartype.
if(vt == VT_BSTR){
// we have an array of strings
// Proceed as above.
}else if(vt == VT_VARIANT){
// we have an array of variants, probably supplied by VBScript
// Read them one by one and use VariantChangeType to get a string
}else{
// We have some other array type we don't support
return E_INVALIDARG;
}

fFeatures needs to equal 2194?

Currently, I am trying hard to get a safearray of variants to work in my c++ code below. As you can see I call QueueInputReport which has a signature of (SAFEARRAY * psainputreport, UNIT timeoutduration):
CComSafeArray<VARIANT> savt;
//LONG j[5];
LONG length = 4;
//SafeArrayLock(psaValues);
for(LONG i = 0; i <= length; ++i)
{
//j[i] = i;
MessageBox(NULL,L"inputreport assigned to variable",NULL,NULL);
//VariantInit(&pDescriptorData[i]);
//pDescriptorData[i].vt = VT_UI1;
//pDescriptorData[i].bVal = inputreport[i];
//SafeArrayPutElement(psaValues,&i,&pDescriptorData[i]);
// VariantClear(&pDescriptorData[i]);
savt.Add(CComVariant(inputreport[i]));
}
//SafeArrayUnlock(psaValues);
MessageBox(NULL,L"data is successfully assigned to safearray",L"correct data format",NULL);
//FADF_STATIC+FADF_FIXEDSIZE+FADF_HAVEVARTYPE+FADF_VARIANT;
/* _TCHAR szBuffer2[100];
_stprintf_s(szBuffer2, _T("%i"),&psaValues->fFeatures);
MessageBox(NULL,L"safe array type",szBuffer2,NULL);*/
piSoftHidDevice1[devindex]->QueueInputReport(savt,8);
piSoftHidDevice1[devindex]->StartProcessing();
piSoftHidDevice1[devindex]->StopProcessing();
Edit: below is the code for queueinputreport which I needed to pass data to.
STDMETHODIMP CHIDDevice::QueueInputReport( SAFEARRAY* psaInputReport, UINT timeoutDuration )
/*++
Routine Description: Queues additional input reports
Arguments:
IdleTimeout - used to set the value of the log level
Return value:
S_OK
--*/
{
VARIANT * pArrayData = NULL;
UINT cbData = 5;
LONG lLBound = 0;
LONG lUBound = 0;
HRESULT hr = S_OK;
HID_INPUT_REPORT inputReport;
BOOLEAN result = TRUE;
// Initialize Structure
inputReport.timeout = timeoutDuration;
inputReport.cbInputReport = 0;
inputReport.pbInputReport = NULL;
MessageBox(NULL,L"report initialized",L"initial report",NULL);
// Get SAFEARRAY size
IfFailHrGo(SafeArrayGetLBound(psaInputReport, 1, &lLBound));
IfFailHrGo(SafeArrayGetUBound(psaInputReport, 1, &lUBound));
IfFalseHrGo(lUBound > lLBound, E_UNEXPECTED);
cbData = lUBound - lLBound + 1;
//psaInputReport->fFeatures = 0x892;
//psaInputReport->fFeatures = 0x892;
inputReport.pbInputReport = (BYTE*)CoTaskMemAlloc( cbData * sizeof(BYTE) );
_TCHAR szBuffer3[100];
_stprintf_s(szBuffer3, _T("%i"), &psaInputReport->fFeatures);
MessageBox(NULL,L"array content features",szBuffer3,NULL);
// If the memory Allocation fails, then fail and exit
if( inputReport.pbInputReport == NULL )
{
hr = E_UNEXPECTED;
goto Exit;
}
//void HUGEP** cast orginally
IfFailHrGo( SafeArrayAccessData( psaInputReport,
reinterpret_cast<void HUGEP**>(&pArrayData) ) );
// Step through the SAFEARRAY and populating only BYTE input report
// and bail out if the type is not correct
for( LONG i = 0; i <= lUBound; ++i )
{
if (pArrayData[i].vt == VT_UI1)
{
inputReport.pbInputReport[i] = pArrayData[i].bVal;
}
else
{
hr = E_FAIL;
goto Exit;
}
}
SafeArrayUnaccessData(psaInputReport);
inputReport.cbInputReport = cbData;
//MessageBox(NULL,L"report being sent next",L"sent report",NULL);
// Add the report to the input queue (does a shallow copy so no need to free the array data)
result = m_InputReportQueue.insert( inputReport );
if (result == FALSE)
{
MessageBox(NULL,L"failed to queue the input",NULL,NULL);
hr = E_FAIL;
}
return hr;
Exit:
SafeArrayUnaccessData(psaInputReport);
if( FAILED(hr) )
{
CoTaskMemFree(inputReport.pbInputReport);
}
return hr;
}
Edit: The problem is I need the fFeatures to equal 2194 and it is currently its a very high number. What could I be doing wrong?
In vbscript, I have some working code for the queueinputreport:
........(too numerous code to list here but it represents the device I am sending input to (i.e. device #1,#2,#3))
Here's some more information on the fFeatures Iam talking about:
http://msdn.microsoft.com/en-us/library/cc237824.aspx
Dim inputreport(5)
inputreport(0) = CByte(0)
inputreport(1) = CByte(100)
inputreport(2) = CByte(100)
inputreport(3) = CByte(0)
inputreport(4) = Cbyte(0)
pisofthiddevice1(i).QueueInputReport(inputreport, 8)
However, when I try replicating this in C++ above it does not work.

ATL/COM: Why does CComSafeArray::GetCount() throw an exception for an empty array?

I'm using the ATL CComSafeArray class, but it seems that GetCount() will throw an exception if the array is empty, for example this code throws an exception:
CComSafeArray<MyStruct> array;
// array.Add(item); // There won't be an exception if I uncomment this line.
array.GetCount();
This is the code of the constructor and GetCount() (from ATL sources):
CComSafeArray() throw() : m_psa(NULL)
{
}
ULONG GetCount(UINT uDim = 0) const
{
ATLASSUME(m_psa != NULL);
ATLASSERT(uDim < GetDimensions());
LONG lLBound, lUBound;
HRESULT hRes = SafeArrayGetLBound(m_psa, uDim+1, &lLBound);
ATLASSERT(SUCCEEDED(hRes));
if(FAILED(hRes))
AtlThrow(hRes);
hRes = SafeArrayGetUBound(m_psa, uDim+1, &lUBound);
ATLASSERT(SUCCEEDED(hRes));
if(FAILED(hRes))
AtlThrow(hRes);
return (lUBound - lLBound + 1);
}
As you can see, the constructor gives a NULL value to m_psa, and in GetCount(), this causes SafeArrayGetLBound() to return an error HRESULT, which causes AtlThrow() to be called.
But I don't understand why GetCount() should throw an exception if the array is empty. Is that the expected behavior?
You have an unbound wrapper for a SAFEARRAY, not an empty array.
If you want an empty SAFEARRAY you can declare one:
CComSafeArray<MyStruct> array((ULONG)0);

How to pass an array of doubles from VB6 to VC++..What is wrong with this code?

How to pass an array of doubles from VB6 to VC++..What is wrong with this code?
VB Code: dSelfCdArr is my array of double values
Public Sub FilterDocTypeByPriv(colEventSets As Collection)
Dim lCount As Long
Dim oColItem As Object
Dim objDBEventSetRow As DB_EventSetRow
Dim evYes As Boolean
Dim dSelfCdArr() As Double
For lCount = 1 To colEventSets.Count
Set objDBEventSetRow = colEventSets(lCount)
ReDim Preserve dSelfCdArr(1 To lCount)
dSelfCdArr(lCount) = CDbl(objDBEventSetRow.dSelf_cd)
Next
Call m_dtsAppForm.DocController.HasPrivCreateResultEventCode(m_dUserId, m_dPositionCd, m_dPPRCd, dSelfCdArr)
End Sub
C++ Idl file:
[id(51), helpstring("method HasPrivCreateResultEventCode")] HRESULT HasPrivCreateResultEventCode([in]double dUserId,[in]double dPosCd,[in]double dPPRCd, [in, out] VARIANT* pEventCode);
C++ Code: I get bad pointers in the first line for VARIANT* pEventCode
STDMETHODIMP CDocumentController::HasPrivCreateResultEventCode(double dUserId,double dPosCd,double dPPRCd, VARIANT* pEventCode)
{
HRESULT hr = E_FAIL;
AFX_MANAGE_STATE(AfxGetStaticModuleState())
if (V_VT(pEventCode) == VT_ARRAY | VT_R8)
{
CComSafeArray<double> arrECode;
arrECode.Attach(pEventCode->parray);
double pVals;
int iCount = arrECode.GetCount();
CMap<double,double,bool,bool> mapEventCds;
for(int iIndex = 0; iIndex < iCount; iIndex++)
{
double pVals = arrECode.GetAt(iIndex);
mapEventCds.SetAt(pVals, false);
std::cout << "element " << iIndex << ": value = " << pVals << std::endl;
}
CheckPrivViewResultEventCds(dUserId, dPosCd, dPPRCd, mapEventCds);
//pEventCode->c
double dEventCd(0.0);
bool bPriv(false);
POSITION pos(mapEventCds.GetStartPosition());
INT_PTR nEventCnt(mapEventCds.GetCount());
CComSafeArray<double> pSafeArraypEventCode = NULL;
for(INT_PTR count(0); count < nEventCnt; ++count)
{
mapEventCds.GetNextAssoc(pos, dEventCd, bPriv);
if (bPriv)
{
pSafeArraypEventCode.Add(dEventCd);
}
}
pEventCode->parray = pSafeArraypEventCode.Detach();
// Empty the CMap
mapEventCds.RemoveAll();
}
return S_OK;
}
Your problem is here:
if (V_VT(pEventCode) == VT_ARRAY | VT_R8)
The VB equivalent to that would be:
If V_VT(pEventCode) = VT_ARRAY Or True Then
//Do stuff
End If
The | VT_R8 is evaluating to boolean true because:
1) == takes precedence over |, so the comparison is performed, THEN VT_R8 is evaluated.
And
2) Anything that is non-zero equals "true" in C. Since VT_R8 gets evaluated by itself (and not as part of the comparison), it is always true.
You need to use parentheses so that your statements are evaluated in the order you want.
Here is the answer..I had to destroy the original safearray data, create a new safearray to fill the data from maps and then copy the new safearray data to the original safearray using SafeArrayCopy.. It worked.
STDMETHODIMP CDocumentController::GetEventCodesWithAddDocumentationPriv(double dUserId,double dPosCd,double dPPRCd,SAFEARRAY** pArrayOfEventCode)
{
HRESULT lResult(S_OK); // return code for OLE functions
// checking if it is a one-dimensional array
if ( (*pArrayOfEventCode)->cDims != 1 )
{
MsgWrite(MSG_DEFAULT, eMsgLog_Commit, _T("PVClinDocMiscCom"), eMsgLvl_Error, _T("CDocumentController::GetEventCodesWithAddDocumentationPriv() SafeArray pEventCode is not one dimensional"));
return(E_FAIL);
}
// locking the array before using its elements
lResult=SafeArrayLock(*pArrayOfEventCode);
if (lResult != S_OK)
{
MsgWrite(MSG_DEFAULT, eMsgLog_Commit, _T("PVClinDocMiscCom"), eMsgLvl_Error, _T("CDocumentController::GetEventCodesWithAddDocumentationPriv() SafeArray pEventCode is not locked"));
SafeArrayUnlock(*pArrayOfEventCode);
return(E_FAIL);
}
double *pArrayOfElements; // pointer to the elements of the array
// using the array
pArrayOfElements=(double*) (*pArrayOfEventCode)->pvData;
CMap<double,double,bool,bool> mapEventCds;
// number of elements in the array
long lElements=(*pArrayOfEventCode)->rgsabound[0].cElements;
double lVal(0);
for (long lCount=0; lCount<lElements; lCount++)
{
lVal = pArrayOfElements[lCount];
mapEventCds.SetAt(lVal, false);
}
CheckPrivViewResultEventCds(dUserId, dPosCd, dPPRCd, mapEventCds);
SafeArrayUnlock(*pArrayOfEventCode);
lResult = SafeArrayDestroyData(*pArrayOfEventCode);
if (lResult != S_OK)
{
MsgWrite(MSG_DEFAULT, eMsgLog_Commit, _T("PVClinDocMiscCom"), eMsgLvl_Error, _T("CDocumentController::GetEventCodesWithAddDocumentationPriv() SafeArray could not be destroyed"));
return(E_FAIL);
}
SAFEARRAYBOUND rgsabound[1]; //Create a one dimensional array
rgsabound[0].lLbound = (*pArrayOfEventCode)->rgsabound->lLbound; //Set the lowerbound for the array
rgsabound[0].cElements = (*pArrayOfEventCode)->rgsabound->cElements; //Set the upperbound for the array
//Create a new safearray of double to fill from the mapeventcodes
SAFEARRAY* newArray = SafeArrayCreate(VT_R8, 1, rgsabound);
double dEventCd(0.0);
bool bPriv(false);
//Get the starting index of the SafeArray
long lEventCdIdx = (*pArrayOfEventCode)->rgsabound->lLbound;
POSITION pos(mapEventCds.GetStartPosition());
while(pos != NULL)
{
mapEventCds.GetNextAssoc(pos, dEventCd, bPriv);
if (bPriv)
{
lResult = SafeArrayPutElement(newArray, &lEventCdIdx, &dEventCd);
if (lResult != S_OK)
{
MsgWrite(MSG_DEFAULT, eMsgLog_Commit, _T("PVClinDocMiscCom"), eMsgLvl_Debug, _T("CDocumentController::GetEventCodesWithAddDocumentationPriv() Failed to add element to array"));
}
lEventCdIdx++;
}
}
// Empty the CMap
mapEventCds.RemoveAll();
//Copy the contents from new safearray to the existing safearray
SafeArrayCopy(newArray, pArrayOfEventCode);
//Destroy the new safearray
SafeArrayDestroy(newArray);
// releasing the array
return S_OK;
}