1

Introduction

I want to query a COM function with Qt. I have a documentation but for VB. Nevertheless this doc says:

Object.Frequencies DataArray

Object     -> An object expression that evaluates to a BKDataSet object.
DataArray  -> An array of strings or an array of values 

The DataArray structure must be declared in the application that is controlling PULSE and the size of the array must be equal to the number of x-axis entries. In Visual Basic, use the DIM (or corresponding) statement to declare the variable and allocate storage space. In other languages use a safearray of strings (VT_BSTR) or reals (VT_R8 for double or VT_R4 for float/single).


Step 1

First I used OLEVIEW to see what the function prototype really looks like.

I got: void Frequencies(VARIANT* FrequencyArray).

So I tried to do it with Qt:

IBKDataSet *data = function->FunctionData();
int nbFrequencies = data->dynamicCall("GetNumberOfXAxisEntries()").toInt();
QList<QVariant> frequencies;
for(int i=0; i<nbFrequencies; j++) {
    frequencies << 0.0;
}
QList<QVariant> parameters;
parameters << QVariant(frequencies);
data->dynamicCall("Frequencies(QVariant&)", parameters);
for(int i=0; i<frequencies.size(); i++) {
    qDebug() << frequencies.at(i);
}

Note: I used a QList<QVariant> as it is shown here.

But I had the following message Type Mismatch in Parameter. Pass an array of type string or real..


Step 2

So I tried with an array of string:

IBKDataSet *data = function->FunctionData();
int nbFrequencies = data->dynamicCall("GetNumberOfXAxisEntries()").toInt();
QList<QString> frequencies;
for(int i=0; i<nbFrequencies; i++) {
    frequencies << "0.0";
}
QList<QVariant> parameters;
parameters << QVariant(frequencies);
data->dynamicCall("Frequencies(QList<QString>&)", parameters);
for(int j=0; j<frequencies.size(); j++) {
    qDebug() << frequencies.at(j);
}

No more error but the returned values are the values set during the initialization (I tried with several ones, not only 0.0).


Step 3

After that, I tried many combinations with no success. So I asked the developers (hard to contact) to provide an example. They sent me the following code (Visual-C++):

IBKDataSetPtr bkDataSet(function->FunctionData);
int nEntries = bkDataSet->GetNumberOfXAxisEntries();
COleSafeArray safeArray;

safeArray.CreateOneDim( VT_R8, nEntries);
COleVariant vDontCare;
bkDataSet->Frequencies(&safeArray);
double* pFrequencyData;
safeArray.AccessData((void**)&pFrequencyData);
CString sMessage;
sMessage.Format("Y[1] = %1.4e ", pFrequencyData[1]);
MessageBox(sMessage);
safeArray.UnaccessData();

Question

How this code can be used in QtCreator?

I realized COleSafeArray is specific to VisualStudio...

I am sorry for the length of the post but this is a while I'm trying to get around this issue without success.


EDIT

Here is a clue on how to do that with the WIN32 API:

QList<double> getCOMValues(IDispatch *dispatch, const QString &method, int arraySize) const {
    QList<double> result;
    HRESULT hr;
    // I don't know how to convert a QString into an OLECHAR*
    OLECHAR * szMember = L"Frequencies";
    DISPID dispid;

    // Retrieve the dispatch identifier for the method.
    // Use defaults where possible.
    hr = dispatch->GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);

    // Test return is OK
    if(FAILED(hr)) {
        qDebug() << "Error while trying to read the " << method;
        return result;
    }

    // Defines a SAFEARRAY.
    SAFEARRAY *psa;
    // Represents the bounds of one dimension of the array.
    SAFEARRAYBOUND freqBound[1];
    // The lower bound of the dimension starts at 0.
    freqBound[0].lLbound = 0;
    // The number of elements in the dimension is the number of frequencies.
    freqBound[0].cElements = arraySize;

    // Creates a new array descriptor, allocates and initializes the data for
    // the array, and returns a pointer to the new array descriptor.
    // VT_R8 represents double.
    // 1 is the number of dimensions in the array.
    psa = SafeArrayCreate(VT_R8, 1, freqBound);

    // Check the array is not NULL.
    if(psa == NULL) {
        qDebug() << "Error while trying to read the " << method;
        return result;
    }

    // This is the default value to put into the array.
    double init[] = {0.0};
    // Populates the array with 0.0.
    for(long index=0;index<arraySize;index++)
    {
        // Stores the data element at the specified location in the array.
        hr = SafeArrayPutElement(psa,&index,init);
        if( FAILED(hr)) {
            qDebug() << "Error while initialising the array for " << method;
        }
    }

    // Describes arguments passed within DISPPARAMS.
    VARIANTARG v[1];
    // A safe array descriptor, which describes the dimensions, size, and in-memory location of the array.
    v[0].parray = psa;
    // The type of data in the union.
    v[0].vt = VT_ARRAY|VT_R8;

    // Contains the arguments passed to a method or property.
    DISPPARAMS params = {v, NULL, 1, 0};

    // To catch errors
    EXCEPINFO pExcepInfo;

    // Provides access to properties and methods exposed by the object.
    hr = dispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, &pExcepInfo, NULL);

    if(FAILED(hr)) {
        if(hr == DISP_E_EXCEPTION) {
            printf("%S\n", pExcepInfo.bstrSource);
            printf("%S\n", pExcepInfo.bstrDescription);
        }

        qDebug() << "Error while trying to read the " << method;
        return result;
    }

    double res = 0;
    for(long idx=0; idx<arraySize; idx++) {
        hr = SafeArrayGetElement(psa, &idx, &res);
        if(hr == S_OK) {
            result.push_back(res);
        } else {
            result.push_back(0.0);
            qDebug() << "Error while trying to read the " << method;
        }
    }

    // Clears the variant.
    hr = VariantClear(v);

    return result;
}

But the Invoke method returns a DISP_E_EXCEPTION: Type Mismatch in Parameter. Pass an array of type string or real.

Maxbester
  • 2,435
  • 7
  • 42
  • 70
  • FWIW, both COleSafeArray and CString are not specific to VisualStudio; they're specific to MFC/ATL. You *can* do this using raw WIN32 API's and avoid the MFC/ATL dependency. However, I find it unlikely you're going to find coding a cross-platform Qt-only solution, especially since you are essentially invoking a 3rd-party library interface that requires platform-dependent code (namely the OLE libs for SAFEARRAY and VARIANT management). If you want to know how to create a SAFEARRAY of any data type and pin it into a VARIANT without MFC/ATL, that can be (and likely has been previously) answered. – WhozCraig Jun 19 '13 at 16:19
  • First thanks for this comment. I wonder why Qt doesn't provide a way to do that. I would like to avoid MFC/ATL but I've never used the WIN32 API to do so. Could you tell me how to do? – Maxbester Jun 19 '13 at 16:24
  • Explaining BSTR, VARIANT, and SAFEARRAY management is far beyond the scope of a single StackOverflow Q&A. I can write the code, but I suspect a magic blob of unintelligible goo isn't what you're looking for. Search SO and MSDN for "Creating VARIANT SAFEARRAY". If you really want to see a sample chunk of code there are tons on the web, but I can write one if I have some time freed up from my ludicrous work schedule. – WhozCraig Jun 19 '13 at 16:35
  • Okay thanks. I added some code at the end of my answer. Could you tell me if I am on the good track? Do you see anything wrong with that code (of course `dataDisp` is initialized but I forgot the line here)? – Maxbester Jun 19 '13 at 16:42
  • Actually not the most outstanding code. (a) Doesn't properly check HRESULT return values (which are the lifeblood of proper-running OLE/COM code), (b) Does not properly initialize the `IDispatch* dataDisp;` interface pointer before usage, (c) Doesn't check for valid return value from `SafeArrayCreate` before using the `psa` var, and (d) Doesn't destroy the safe array when finished (which can/should be done with a `VariantClear` of `v`) after the invoke is finished. There is plenty wrong with the code, but at least you can see how some of it works, which is better than you had before. – WhozCraig Jun 19 '13 at 16:51
  • Yes I know it was just a start. I will update it tomorrow cause I'm out of the office right now. – Maxbester Jun 19 '13 at 17:32
  • `Invoke` throws `DISP_E_EXCEPTION`. The description is `Type Mismatch in Parameter. Pass an array of type string or real`. See my code in the question. – Maxbester Jun 20 '13 at 16:05

1 Answers1

0

Found the problem. Very simple!

IBKDataSet *data = function->FunctionData();
int nbFrequencies = data->dynamicCall("GetNumberOfXAxisEntries()").toInt();
QList<QString> frequencies;
for(int i=0; i<nbFrequencies; i++) {
    frequencies << "0.0";
}
QList<QVariant> parameters;
parameters << QVariant(frequencies);
data->dynamicCall("Frequencies(QList<QString>&)", parameters);
frequencies = parameters.first().toStringList();
for(int j=0; j<frequencies.size(); j++) {
    qDebug() << frequencies.at(j);
}

I had to read the first element of parameters and convert it to a QStringList...

Maxbester
  • 2,435
  • 7
  • 42
  • 70