How to iterate through SAFEARRAY ** - c++

how to iterate through C++ safearray pointer to pointer and access its elements.
I tried to replicate the solution posted by Lim Bio Liong
http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/022dba14-9abf-4872-9f43-f4fc05bd2602
but the strangest thing is that the IDL method signature comes out to be
HRESULT __stdcall GetTestStructArray([out] SAFEARRAY ** test_struct_array);
instead of
HRESULT __stdcall GetTestStructArray([out] SAFEARRAY(TestStruct)* test_struct_array);
Any ideas?
thanks in advance

Safearrays are created with SafeArrayCreate or SafeArrayCreateVector, but as you ask about iterating over a SAFEARRAY, let's say you already have a SAFEARRAY returned by some other function. One way is to use SafeArrayGetElement API which is especially convenient if you have multidimensional SAFEARRAYs, as it allows, IMO, a bit easier specifying of the indices.
However, for vectors (unidimensional SAFEARRAY) it is faster to access data directly and iterate over the values. Here's an example:
Let's say it's a SAFEARRAY of longs, ie. VT_I4
// get them from somewhere. (I will assume that this is done
// in a way that you are now responsible to free the memory)
SAFEARRAY* saValues = ...
LONG* pVals;
HRESULT hr = SafeArrayAccessData(saValues, (void**)&pVals); // direct access to SA memory
if (SUCCEEDED(hr))
{
long lowerBound, upperBound; // get array bounds
SafeArrayGetLBound(saValues, 1 , &lowerBound);
SafeArrayGetUBound(saValues, 1, &upperBound);
long cnt_elements = upperBound - lowerBound + 1;
for (int i = 0; i < cnt_elements; ++i) // iterate through returned values
{
LONG lVal = pVals[i];
std::cout << "element " << i << ": value = " << lVal << std::endl;
}
SafeArrayUnaccessData(saValues);
}
SafeArrayDestroy(saValues);

MSDN SafeArrayGetElement function gives you a code snippet on using SafeArrayGetElement to obtain individual object to array.
SAFEARRAY structure and SafeArray* functions explain the available API.
In ATL/MFC project you would want to use wrapper classes e.g. CComSafeArray to make things simpler and easier. See Simplifying SAFEARRAY programming with CComSafeArray on this.

Related

How to directly access to what's in VARIANT variables in C++?

My program uses an external ocx library and receives data through it. The code below shows how it works.
VARIANT varArrItem, varArrData;
ocx_instance.GetItemArr(real, &varArrItem); // the library provides GetItemArr
// 1) receives data
long lLBound, lUBound;
VARIANT varItem, varData;
long index[2];
index[0] = 0;
index[1] = 0;
COleSafeArray* pSafeItemArr = (COleSafeArray*)&varArrItem; // 2) casts varArrItem to COleSafeArray
CString strItem;
CStringArray arrItem;
pSafeItemArr->GetLBound(1, &lLBound);
pSafeItemArr->GetUBound(1, &lUBound);
int nItemCnt = (lUBound - lLBound + 1);
for (int i = 0; i < nItemCnt; i++)
{
index[0] = i;
VariantInit(&varItem);
pSafeItemArr->GetElement(index, (void *)&varItem); // 3) gets its values using GetElement
strItem = varItem.bstrVal;
arrItem.Add(strItem);
VariantClear(&varItem);
}
A big problem of the program is that this code is run whenever new data arrives, which is quite often, and it consumes a lot of resources. So, I'd like to simplify the code and get just contents of varArrItem, as strings or an array of structs of strings, for example.
varArrItem.vt gives me 8204 and it's said that it consists of 8192(VT_ARRAY) and 12(VT_VARIANT). I'm stuck here and don't know what to do after this. How can I simply get what's in them? Is it possible to access to them without using COleSafeArray?
You don't NEED to use COleSafeArray, it is just a wrapper for convenience. You could just extract the SAFEARRAY* pointer directly from varArrItem and then use the SafeArray APIs directly: SafeArrayGet(L|U)Bound(), SafeArrayGetElement(), etc, though if performance is important then consider using SafeArrayAccessData() to access the VARIANT[] array directly, and thus its BSTR pointers. The less copying of data you do, the faster the code will run. The only copy of data this code actually needs to make is the assignment of the initial VARIANT and each CString you add to the CStringArray:
VARIANT varArray;
ocx_instance.GetItemArr(real, &varArray);
LPSAFEARRAY psa = varArrar.parray;
LONG lLBound, lUBound;
SafeArrayGetLBound(psa, 1, &lLBound);
SafeArrayGetUBound(psa, 1, &lUBound);
CStringArray arrItem;
VARIANT *varArrayData;
if (SUCCEEDED(SafeArrayAccessData(psa, (void**) &varArrayData)))
{
int nItemCnt = (lUBound - lLBound + 1);
for (int i = 0; i < nItemCnt; i++)
{
CString strItem = varArrayData[i].bstrVal;
arrItem.Add(strItem);
}
SafeArrayUnaccessData(psa);
}

Allocate multiple structs in memory

i need pass multiple values to memory, i need make various country to CEN/XFS.
This api: CashDispenser - CDM
Struct reference: WFSCDMCURRENCYEXP
How am i trying to do:
HRESULT WINAPI WFPGetInfo(HSERVICE hService, DWORD dwCategory, LPVOID lpQueryDetails, DWORD dwTimeOut, HWND hWnd, REQUESTID ReqID) {
WFSRESULT * lpWFSResult;
WFSCDMSTATUS CdmStatus;
WFSCDMCAPS CdmCapabilities;
WFSCDMCASHUNIT CdmCash;
WFSCDMCURRENCYEXP CdmCurrency;
HRESULT result;
result = WFMAllocateBuffer(sizeof(WFSRESULT), WFS_MEM_ZEROINIT | WFS_MEM_SHARE, (void**)&lpWFSResult);
if(result != WFS_SUCCESS){
return WFS_ERR_INTERNAL_ERROR;
}
if(dwCategory == WFS_INF_CDM_CURRENCY_EXP){
const int countCurrencies = 2;
WFSCDMCURRENCYEXP** ppCdmCurrencies;
result = WFMAllocateMore(sizeof(WFSCDMCURRENCYEXP*) * (countCurrencies+1), lpWFSResult, (void**)&ppCdmCurrencies);
lpWFSResult->hService=hService;
lpWFSResult->RequestID=ReqID;
lpWFSResult->u.dwEventID=WFS_INF_CDM_CURRENCY_EXP;
lpWFSResult->hResult=WFS_SUCCESS;
result = WFMAllocateMore(sizeof(WFSCDMCURRENCYEXP), lpWFSResult, (void**)&ppCdmCurrencies[0]);
WFSCDMCURRENCYEXP& cmdCurrency0(*ppCdmCurrencies[0]);
memcpy(cmdCurrency0.cCurrencyID, "AED", 3);
cmdCurrency0.sExponent = 0;
WFSCDMCURRENCYEXP& cmdCurrency1(*ppCdmCurrencies[1]);
memcpy(cmdCurrency1.cCurrencyID, "AFA", 3);
cmdCurrency1.sExponent = 0;
lpWFSResult->lpBuffer = ppCdmCurrencies;
logFile.close();
}
}
Your code looks very C-ish.
An idiomatic way to do this in c++ would be:
struct WFSCDMCURRENCYEXP
{
std::string cCurrencyID;
SHORT sExponent;
};
std::vector<WFSCDMCURRENCYEXP> CdmCurrencies {
{ "ARG", 3 } ,
{ "EUA", 3 } ,
// lots more countries
};
Update:
I just noticed you apparently interact with a c-style API, and using that struct might be required in its original form.
Though in c++ you still can use a std::vector to manage a dynamically allocated, contiguous array of that struct:
typedef struct _wfs_cdm_currency_exp
{
CHAR cCurrencyID[3];
SHORT sExponent;
} WFSCDMCURRENCYEXP, * LPWFSCDMCURRENCYEXP;
std::vector<WFSCDMCURRENCYEXP> CdmCurrencies {
{ { 'A', 'R', 'G' }, 3 } , // Note that the cCurrencyID is a non null terminated
// array here
{ { 'E', 'U', 'A' }, 3 } ,
// lots more countries
};
LPWFSCDMCURRENCYEXP pCdmCurrencies = &CdmCurrencies[0];
Declare an array:
WFSCDMCURRENCYEXP CdmCurrency[2];
memcpy( CdmCurrency[0].cCurrencyID, "ARG", 3);
CdmCurrency[0].sExponent = 0;
memcpy( CdmCurrency[1].cCurrencyID, "EUA", 3);
CdmCurrency[1].sExponent = 0;
memcpy(lpWFSResult->lpBuffer, CdmCurrency, 2*sizeof(WFSCDMCURRENCYEXP));
// ^^
Don't forget you will need to copy more memory.
Also note I have fixed the setting of .cCurrencyID - a character literal (with single quotes) can only contain a single character. To move multiple characters, you will need to call memcpy from a string. Normally I would suggest using std::string rather than char [3], but you can't use memcpy if you do, and it probably wouldn't be a good idea to pass a std::string across a DLL boundary.
I think you try to handle WFS_INF_CDM_CURRENCY_EXP message to get information about currencies in your CDM.
Read XFS specification carefully:
Output Param LPWFSCDMCURRENCYEXP *lppCurrencyExp; Pointer to a NULL-terminated array of pointers to WFSCDMCURRENCYEXP structures
This means that you must allocate an array of pointers to WFSCDMCURRENCYEXP with size N+1 and set last item to null.
In CEN/XFS you cannot use standard new or malloc for memory allocation.
You need to use WFMAllocateBuffer and WFMAllocateMore to allocate memory for XFS structures that you return to caller.
For you task yo need something like this:
HRESULT WINAPI WFPGetInfo(HSERVICE hService, DWORD dwCategory, LPVOID lpQueryDetails, DWORD dwTimeOut, HWND hWnd, REQUESTID ReqID) {
WFSRESULT * lpWFSResult;
WFSCDMSTATUS CdmStatus;
WFSCDMCAPS CdmCapabilities;
WFSCDMCASHUNIT CdmCash;
WFSCDMCURRENCYEXP CdmCurrency;
HRESULT result;
result = WFMAllocateBuffer(sizeof(WFSRESULT), WFS_MEM_ZEROINIT | WFS_MEM_SHARE, (void**)&lpWFSResult);
if(result != WFS_SUCCESS){
return WFS_ERR_INTERNAL_ERROR;
}
if(dwCategory == WFS_INF_CDM_CURRENCY_EXP){
const int countCurrencies = 2;
WFSCDMCURRENCYEXP** ppCdmCurrencies;
result = WFMAllocateBuffer(sizeof(WFSCDMCURRENCYEXP*) * (countCurrencies+1), WFS_MEM_ZEROINIT | WFS_MEM_SHARE, (void**)&ppCdmCurrencies);
lpWFSResult->hService=hService;
lpWFSResult->RequestID=ReqID;
lpWFSResult->u.dwEventID=WFS_INF_CDM_CURRENCY_EXP;
lpWFSResult->hResult=WFS_SUCCESS;
result = WFMAllocateMore(sizeof(WFSCDMCURRENCYEXP), lpWFSResult, (void**)&ppCdmCurrencies[0]);
WFSCDMCURRENCYEXP& cmdCurrency0(*ppCdmCurrencies[0]);
memcpy(cmdCurrency0.cCurrencyID, "AED", 3);
cmdCurrency0.sExponent = 0;
result = WFMAllocateMore(sizeof(WFSCDMCURRENCYEXP), lpWFSResult, (void**)&ppCdmCurrencies[1]);
WFSCDMCURRENCYEXP& cmdCurrency1(*ppCdmCurrencies[1]);
memcpy(cmdCurrency1.cCurrencyID, "AFA", 3);
cmdCurrency1.sExponent = 0;
lpWFSResult->lpBuffer = ppCdmCurrencies;
logFile.close();
return WFS_SUCCESS;
}
}
That's not so simple to manipulate with XFS. It's too many complex API structures with different rules of allocation and data representation. Please read carefully XFS manuals. In the first book ftp://ftp.cen.eu/CWA/CEN/WS-XFS/CWA16926/CWA%2016926-1.pdf many conceptual things is described. About configuration, memory mangement and so on.
You can call malloc to allocate multiple structs, if they are what is called POD - plain old data - or structures with no destructors, member functions, or member classes with these characteristics.
The only reason for doing that is to be compatible with C, however, or for writing very low level code.
As a general rule, you want to create an std::vector. The vector handles all the memory for you, and you can push_back as many members as you require. Use reserve (or resize) if efficiency is important. There's also a member called data() which is there for C compatibility (however C can't call your vector, at least not easily).
your code keeps throwing WFS_ERR_INTERNAL_ERROR because you havent got a fully functional XFS environment setup, setup a proper configuration, or emulate one (registry keys, sdk, dlls, etc) and then test it again, make sure your code includes the proper headers and make sure you are linking to msxfs.lib, xfs_conf.lib and xfs_supp.lib

When i pass an array of strings from VBA to c++ and use them as keys in a map, why am i not able to retrieve data (using the keys) from the map?

I am trying to pass 2 arrays of strings from vba to c++ and use them as keys to a nested map. The data corresponding to the keys is passed as an array of doubles. First array of strings (VolArrayTenor_Destination) contains "ON", "1W", "1M", "3M", "6M" and the other array of strings (VolArrayData_Destination) contains "ATMVOL", "25RR", "10FLY". I am trying to get the data using keys in the code and it returns a 0 value, my guess is that it cannot find the keys from the map.
double convert2Maps(double* VolArray_destination, BSTR* VolArrayTenor_destination, BSTR* VolArrayData_destination,
long VolArrayTenorLen, long VolArrayDataLen )
{
double a ,x;
BSTR* b;
BSTR* c;
map<BSTR, map<BSTR, double> >* nestedMap = new map<BSTR, map<BSTR, double> >;
map<BSTR, map<BSTR, double> > &nestedMapAlais = *nestedMap;
for (int i=0; i<VolArrayTenorLen;++i)
for (int j=0; j<VolArrayDataLen;++j)
{
{
a = *(VolArray_destination + (i * VolArrayDataLen) + (j));
b = ((VolArrayTenor_destination + i));
c = ((VolArrayData_destination + j));
nestedMapAlais[*b][*c] = a;
}
}
BSTR ON=L"1Y";
BSTR VOLATM=L"VOLATM";
x=nestedMapAlais[ON][VOLATM];
return (x);
}
The type BSTR is a pointer, so you use the address stored in the pointer as a key in the map, not the string value pointed to.
Trying to do a lookup with an identical string value, but stored at a different address will not find the original entry in the map.
In C++ it is common to use a map<std::string, double> to avoid this problem.
When handling BSTRs, keep in mind that a BSTR is not a normal string. BSTR values must be allocated using Win32 API calls like SysAllocString(), and freed using SysFreeString(). In addition, BSTR can be NULL (which is a valid empty string).
Some suggestions:
Make sure your VBA arrays are fixed-sized. Otherwise, they are stored as SAFEARRAY, not a C-style array.
Use proper API to allocate and free BSTRs:
BSTR sOn = SysAllocStringW(L"1Y");
...
SysFreeString(sOn);
Instead of dealing with BSTRs directly, convert them into strings of wide chars. (Note that a BSTR encoding an empty string can be (but not necessarily is) a NULL pointer). Here's one example of such conversion function:
std::wstring BstrToString(BSTR s)
{
return std::wstring(s, s + SysStringLen(sMyHello));
}
To summarize:
map<std::wstring, map<std::wstring, double> > nestedMap;
for (int i=0; i<VolArrayTenorLen;++i)
{
for (int j=0; j<VolArrayDataLen;++j)
{
double a = *(VolArray_destination + (i * VolArrayDataLen) + (j));
BSTR b = *(VolArrayTenor_destination + i);
BSTR c = *(VolArrayData_destination + j);
nestedMapAlais[BstrToString(b)][BstrToString(c)] = a;
}
}
x=nestedMapAlais[L"1Y"][L"VOLATM"];

DsGetDomainControllerInfo returns a "Pointer to a pointer variable that receives an array"? I don't get it

Most of my experience is with C#...so I'm still getting used to C++.
I'm trying to call DsGetDomainControllerInfo to get all of the domain controllers in the domain. Here's a link to the MSDN docs for that call:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms675987(v=vs.85).aspx
The fourth parameter returns the number of DC's that should be in the array of DS_DOMAIN_CONTROLLER_INFO_* structures.
I'm getting the first DS_DOMAIN_CONTROLLER_INFO_* in the array, but it throws an unhandled exception on the second. The last argument is a void**...I'm casting to that, but I doubt that's the right thing to do.
Here's my code:
PDOMAIN_CONTROLLER_INFO logonDomainController;
DsGetDcName(NULL, NULL, NULL, NULL, 0, &logonDomainController);
wstring domCon = logonDomainController->DomainControllerName;
wstring domNam = logonDomainController->DomainName;
HANDLE domHan;
DsBindWithCred(domCon.c_str(), domNam.c_str(), NULL, &domHan);
DWORD count = 0;
DS_DOMAIN_CONTROLLER_INFO_3 *dci[100] = { NULL };
DsGetDomainControllerInfo(domHan, domNam.c_str(), 3, &count, (void**)dci);
for (size_t i = 0; i < count; i++)
{
wcout << dci[i]->DnsHostName << endl;
}
I read the documentation as: you have to declare DS_DOMAIN_CONTROLLER_INFO_3 *dci; and pass its address as (VOID**) &dci (in the sense of a result/"out" parameter), so dci can be assigned the base address of the ..INFO_3 array by the callee. You can still access the elements of the array with dci[i].
I think it becomes clearer when reading the linked documentation for the DsFreeDomainControllerInfo function (which takes the same pointer as "in" parameter).

Array as out parameter in c++

I created a function that returns an error code (ErrCode enum) and pass two output parameters. But when I print the result of the function, I don't get the correct values in the array.
// .. some codes here ..
ErrCode err;
short lstCnt;
short lstArr[] = {};
err = getTrimmedList(lstArr, &lstCnt);
// list returned array (for comparison)
for (int i=0; i<lstCnt; ++i)
printf("lstArr[%3d] = %d", i, lstArr[i]);
// .. some codes here ..
The getTrimmedList function is like this:
ErrCode getTrimmedList(short* vList, short* vCnt)
{
short cnt;
ErrCode err = foo.getListCount(FOO_TYPE_1, &cnt);
if (NoError!=err) return err;
short* list = new short [cnt];
short total = 0;
for (short i=0; i<cnt; ++i)
{
FooBar bar = foo.getEntryByIndex(FOO_TYPE_1, i);
if (bar.isDeleted) continue;
list[total] = i;
++total;
}
*vCnt = total;
//vList = (short*)realloc(index, sizeof(short)*total);
vList = (short*)malloc(sizeof(short)*total);
memcpy(vList, list, sizeof(short)*total)
// list returned array (for comparison)
for (int i=0; i<lstCnt; ++i)
printf("lstArr[%3d] = %d", i, lstArr[i]);
return NoError;
}
where:
foo is an object that holds arrays of FooBar objects
foo.getListCount() returns the number of objects with type FOO_TYPE_1
FOO_TYPE_1 is the type of object we want to take/list
foo.getEntryByIndex() returns the ith FooBar object with type FOO_TYPE_1
bar.isDeleted is a flag that tells if bar is considered as 'deleted' or not
What's my error?
Edit:
Sorry, I copied a wrong line. I commented it above and put the correct line.
Edit 2
I don't have control over the returns of foo and bar. All their function returns are ErrCode and the outputs are passed through parameter.
Couple of questions before I can answer your post...
Where is "index" defined in:
vList = (short*)realloc(index, sizeof(short)*total);
Are you leaking the memory associated with:
short* list = new short [cnt];
Is it possible you have accidentally confused your pointers in memory allocation? In any case, here is an example to go from. You have a whole host of problems, but you should be able to use this as a guide to answer this question as it was originally asked.
WORKING EXAMPLE:
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int getTrimmedList(short** vList, short* vCnt);
int main ()
{
// .. some codes here ..
int err;
short lstCnt;
short *lstArr = NULL;
err = getTrimmedList(&lstArr, &lstCnt);
// list returned array (for comparison)
for (int i=0; i<lstCnt; ++i)
printf("lstArr[%3d] = %d\n", i, lstArr[i]);
// .. some codes here ..
return 0;
}
int getTrimmedList(short** vList, short* vCnt)
{
short cnt = 5;
short* list = new short [cnt];
short* newList = NULL;
short total = 0;
list[0] = 0;
list[1] = 3;
list[2] = 4;
list[3] = 6;
total = 4;
*vCnt = total;
newList = (short*)realloc(*vList, sizeof(short)*total);
if ( newList ) {
memcpy(newList, list, sizeof(short)*total);
*vList = newList;
} else {
memcpy(*vList, list, sizeof(short)*total);
}
delete list;
return 0;
}
You have serious problems.
For starters, your function has only one output param as you use it: vCnt.
vList you use as just a local variable.
realloc is called with some index that we kow nothing about, not likely good. It must be something got from malloc() or realloc().
The allocated memory in vList is leaked as soon as you exit getTrimmedList.
Where you call the function you pass the local lstArr array as first argument that is not used for anything. Then print the original, unchanged array, to bounds in cnt, while it has 0 size still -- behavior is undefined.
Even if you managed to pass that array by ref, you could not reassign it to a different value -- C-style arrays can't do that.
You better use std::vector that you can actually pass by reference and fill in the called function. eliminating the redundant size and importantly the mess with memory handling.
You should use std::vector instead of raw c-style arrays, and pass-by-reference using "&" instead of "*" here. Right now, you are not properly setting your out parameter (a pointer to an array would look like "short **arr_ptr" not "short *arr_ptr", if you want to be return a new array to your caller -- this API is highly error-prone, however, as you're finding out.)
Your getTrimmedList function, therefore, should have this signature:
ErrCode getTrimmedList(std::vector<short> &lst);
Now you no longer require your "count" parameters, as well -- C++'s standard containers all have ways of querying the size of their contents.
C++11 also lets you be more specific about space requirements for ints, so if you're looking for a 16-bit "short", you probably want int16_t.
ErrCode getTrimmedList(std::vector<int16_t> &lst);
It may also be reasonable to avoid requiring your caller to create the "out" array, since we're using smarter containers here:
std::vector<int16_t> getTrimmedList(); // not a reference in the return here
In this style, we would likely manage errors using exceptions rather than return-codes, however, so other things about your interface would evolve, as well, most likely.