Calling COM from C++ using CoCreateInstance and IDispatch, with numerous parameters in DISPPARAMS incl. SAFEARRAY

515 views Asked by At

I have a COM object which exposes a function. I would like to pass parameters to this function and receive a return value. I'm using C++ with CoCreateInstance(). The error I receive is:

hr = 0x8002000e : Invalid number of parameters.

I'm reasonably sure that I have the correct number of parameters, which I can view in OleView:

[id(0x68030001), propget]
double My_function(
                [in, out] double* PdblPrice, 
                [in, out] DATE* PdateStartDate, 
                [in, out] short* PintFlag, 
                [in, out] VARIANT_BOOL* PbolXP, 
                [in, out] SAFEARRAY(double)* PdblScale),
                [out, retval] double*);

A summary of my code is as follows, and it works up to the point marked below:

#include <windows.h>
#include <objbase.h>
#include <comutil.h>
#include <vector>
#include <atlcomcli.h>

int main()
{
    HRESULT hr;

    // Create an instance of COM object
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    CLSID clsid;
    HRESULT nResult1 = CLSIDFromProgID(OLESTR("My_Library.clsMy_Library"), &clsid);

    IUnknown* pUnknown;
    hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnknown);

    // Get the IDispatch interface
    IDispatch* pDispatch;
    hr = pUnknown->QueryInterface(IID_IDispatch, (void**)&pDispatch);

    // Call the Invoke method
    DISPID dispid;
    BSTR bstrFunction = SysAllocString(L"My_function");
    hr = pDispatch->GetIDsOfNames(IID_NULL, &bstrFunction, 1, LOCALE_USER_DEFAULT, &dispid);

    // ALL OF THE ABOVE WORKS.

    // Prepare the arguments for the method call
    // first convert a std::vector to SAFEARRAY
    std::vector<double> _PdblScale = { 0, 0.25, 0.5, 0.75, 1.0, 0, 0, 0.5, 1, 1 };
    SAFEARRAY* psa = SafeArrayCreateVector(VT_R8, 0, _PdblScale.size());
    int* pData;
    HRESULT hr_ = SafeArrayAccessData(psa, (void**)&pData);
    if (SUCCEEDED(hr_))
    {
        for (unsigned int i = 0; i < _PdblScale.size(); i++)
        {
            pData[i] = _PdblScale[i];
        }
        SafeArrayUnaccessData(psa);
    }

    DISPPARAMS dispparams;
    dispparams.cArgs = 5;
    dispparams.rgvarg = new VARIANT[5];
    dispparams.cNamedArgs = 5;


    VARIANT PdblPrice;
    PdblPrice.vt = VT_R8;
    PdblPrice.dblVal = 28.0;
    dispparams.rgvarg[0] = PdblPrice;

    VARIANT PdateStartDate;
    PdateStartDate.vt = VT_DATE;
    PdateStartDate.date = 41052;
    dispparams.rgvarg[1] = PdateStartDate;

    VARIANT PintFlag;
    PintFlag.vt = VT_I2;
    PintFlag.iVal = 1;
    dispparams.rgvarg[2] = PintFlag;

    VARIANT PbolXP;
    PbolXP.vt = VT_BOOL;
    PbolXP.boolVal = false;
    dispparams.rgvarg[3] = PbolXP;

    VARIANT PdblScale;
    PdblScale.vt = VT_SAFEARRAY;
    PdblScale.pvRecord = psa;
    dispparams.rgvarg[4] = PdblScale;

    VARIANT varResult;
    VariantInit(&varResult);
    EXCEPINFO excepinfo;
    memset(&excepinfo, 0, sizeof(excepinfo));
    UINT nArgErr = (UINT)-1;

    // Invoke the method  ## THIS IS WHERE hr returns 0x8002000e : Invalid number of parameters
    hr = pDispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, &varResult, &excepinfo, &nArgErr);
    if (FAILED(hr))
    {
        printf("Failed to invoke method.");
        pDispatch->Release();
        pUnknown->Release();
        CoUninitialize();
        return 1;
    }

    // Print the result
    printf("Result: %d\n", varResult.intVal);

    // Clean up
    VariantClear(&varResult);
    pDispatch->Release();
    pUnknown->Release();
    CoUninitialize();

    return 0;
}

I see from IDispatch Invoke() returns Type mismatch that the arguments should be in reverse order. I have tried that, ie. used [4], [3], [2], etc instead of [0], [1], etc above, but this still gives the error.

Any suggestions as to where I might be going wrong?

By the way, the COM is from a 32bit DLL, and I am compiling my code to x86.

1

There are 1 answers

7
Remy Lebeau On BEST ANSWER

There are many problems with your code:

  • lack of error handling.

  • when creating the COM object, you don't need to get its IUnknown just to immediately query it for IDispatch. You can get its IDispatch directly.

  • memory leaks on bstrFunction and dispparams.rgvarg.

  • you are creating a SAFEARRAY of VT_R8 (double) elements, but you are using an int* pointer to populate its values. You need to use a double* pointer instead.

  • you are not populating the DISPPARAMS or the VARIANTs correctly. The parameters have to be stored in the DISPPARAMS in revere order. And the VARIANTs need to use the VT_BYREF flag, which means they need to point at external variables that hold the actual values, rather than storing the values inside the VARIANTs themselves.

  • calling IDispatch::Invoke() with the wrong flag. You need to use DISPATCH_PROPERTYGET instead of DISPATCH_METHOD since the method is marked as propget in the IDL.

  • not using VARIANT_BOOL correctly for the bolXP parameter.

  • after invoking the method, you are printing out the wrong field of varResult. The method is declared as returning a double in the IDL, not an int.

With all of that said, try something more like this:

#include <windows.h>
#include <objbase.h>
#include <comutil.h>
#include <comdef.h>
#include <atlcomcli.h>
#include <vector>

// tweaked from https://devblogs.microsoft.com/oldnewthing/20040520-00/?p=39243
class CCoInitializeEx {
  HRESULT m_hr;
public:
  CCoInitializeEx() : m_hr(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)) { }
  ~CCoInitializeEx() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
  operator HRESULT() const { return m_hr; }
};

int main()
{
    HRESULT hr;

    // Initialize COM

    CCoInitializeEx init;
    hr = init;
    if (FAILED(hr)) {
        printf("Failed to init COM.");
        return -1;
    }

    // Create an instance of COM object and get its IDispatch interface

    CLSID clsid;
    hr = CLSIDFromProgID(OLESTR("My_Library.clsMy_Library"), &clsid);
    if (FAILED(hr)) {
        printf("Failed to get CLSID.");
        return -1;
    }

    IDispatchPtr pDispatch;
    hr = pDispatch.CreateInstance(clsid);
    if (FAILED(hr)) {
        printf("Failed to create COM object.");
        return -1;
    }

    // Call the Invoke method

    DISPID dispid;
    _bstr_t bstrFunction = OLESTR("My_function");
    LPOLESTR pbstrFunction = bstrFunction;
    hr = pDispatch->GetIDsOfNames(IID_NULL, &pbstrFunction, 1, LOCALE_USER_DEFAULT, &dispid);
    if (FAILED(hr)) {
        printf("Failed to get DispID.");
        return -1;
    }

    // ...

    // first convert a std::vector to SAFEARRAY
    // TODO: wrap the SAFEARRAY inside a RAII class...

    std::vector<double> vecDblScale = { 0, 0.25, 0.5, 0.75, 1.0, 0, 0, 0.5, 1, 1 };
    SAFEARRAY* psa = SafeArrayCreateVector(VT_R8, 0, vecDblScale.size());
    if (!psa) {
        printf("Failed to allocate SAFEARRAY.");
        return -1;
    }

    double* pData;
    hr = SafeArrayAccessData(psa, reinterpret_cast<void**>(&pData));
    if (FAILED(hr))
        printf("Failed to access SAFEARRAY data.");
        SafeArrayDestroy(psa);
        return -1;
    }

    for (size_t i = 0; i < vecDblScale.size(); ++i)
    {
        pData[i] = vecDblScale[i];
    }
    // alternatively:
    //
    // #include <algorithm>
    // std::copy(vecDblScale.begin(), vecDblScale.end(), pData);

    SafeArrayUnaccessData(psa);

    // Prepare the arguments for the method call

    std::vector<VARIANT> vecRgvarg(5);

    DOUBLE dblPrice = 28.0;
    vecRgvarg[4].vt = VT_R8 | VT_BYREF;
    vecRgvarg[4].pdblVal = &dblPrice;

    DATE dateStartDate = 41052;
    vecRgvarg[3].vt = VT_DATE | VT_BYREF;
    vecRgvarg[3].pdate = &dateStartDate;

    short intFlag = 1;
    vecRgvarg[2].vt = VT_I2 | VT_BYREF;
    vecRgvarg[2].piVal = &intFlag;

    VARIANT_BOOL bolXP = VARIANT_FALSE;
    vecRgvarg[1].vt = VT_BOOL | VT_BYREF;
    vecRgvarg[1].pboolVal = &bolXP;

    vecRgvarg[0].vt = VT_R8 | VT_ARRAY | VT_BYREF;
    vecRgvarg[0].pparray = &psa;

    DISPPARAMS dispparams = {};
    dispparams.cArgs = vecRgvarg.size();
    dispparams.rgvarg = vecRgvarg.data();

    // Invoke the method

    _variant_t varResult;
    EXCEPINFO excepinfo = {};
    UINT nArgErr = (UINT)-1;

    hr = pDispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &varResult, &excepinfo, &nArgErr);
    if (FAILED(hr)) {
        printf("Failed to invoke method.");
        SafeArrayDestroy(psa);
        return -1;
    }

    // Print the result
    printf("Result: %f\n", varResult.dblVal);

    // Clean up
    SafeArrayDestroy(psa);

    return 0;
}