0

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Gary
  • 13
  • 1
  • 4
  • Off the top, you are passing `DISPATCH_METHOD` instead of `DISPATCH_PROPERTYGET`. Observe that the function is marked `propget` in the IDL. – Igor Tandetnik Jan 17 '23 at 15:17
  • 1
    You claim to pass 5 named args, but `rgdispidNamedArgs` member of `DISPPARAMS` is not set. You likely want to set `cNamedArgs` to 0. – Igor Tandetnik Jan 17 '23 at 15:20
  • 1
    Parameters must be placed into `dispparams.rgvarg` in reverse order: the rightmost parameter at index 0, the leftmost at `cArgs-1` – Igor Tandetnik Jan 17 '23 at 15:22
  • 1
    `[in, out] double* PdblPrice` must be passed as `VT_R8 | VT_BYREF`, with `double*` pointer in `VARIANT::pdblVal` member. Same with all the other `[in, out]` parameters. – Igor Tandetnik Jan 17 '23 at 15:23
  • Thanks for these very helpful comments, @IgorTandetnik. I've corrected `DISPATCH_METHOD` to `DISPATCH_PROPERTYGET` (nicely spotted!), and `cNamedArgs` to 0. I've also reversed the order of `dispparams.rgvarg` and added `| VT_BYREF` to all `[in, out]` parameters. The error message has now changed to **hr = 0x80020005 : Type mismatch.** which I think is progress :) Any further suggestions? – Gary Jan 17 '23 at 15:50
  • @IgorTandetnik: just to clarify: is the following correct for the `double*` variable: `VARIANT PdblPrice; PdblPrice.vt = VT_R8 | VT_BYREF; PdblPrice.dblVal = 28.0; dispparams.rgvarg[28] = PdblPrice;` **OR** should the third line be `PdblPrice.pdblVal`. I'm then not sure what does on the right hand side, as `= 28` gives an error: `a value of type double cannot be assigned to an entity of type DOUBLE` – Gary Jan 17 '23 at 17:06
  • 1
    @Gary You need to use something like this: `DOUBLE value = 28.0; VARIANT dblPrice; dblPrice.vt = VT_R8 | VT_BYREF; dblPrice.pdblVal = &value; dispparams.rgvarg[28] = dblPrice;` Similarly for all of the other `VT_BYREF` values. – Remy Lebeau Jan 17 '23 at 17:46
  • 1
    @SimonMourier No, that's not how dispinterfaces are defined in the IDL. The return type is specified as a return type. There's a dedicated place for the return value in `DISPPARAMS`, outside of `rgvarg` – Igor Tandetnik Jan 17 '23 at 22:09
  • @SimonMourier - I think that @Igor has answered your question, but I noticed that I didn't include the full output from OleView.exe: `[id(0x68030001), propget] double My_function( [in, out] double* PdblPrice, ....... [out, retval] double*);` – Gary Jan 17 '23 at 22:12

1 Answers1

2

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;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for this detailed reply @RemyLebeau. Just one part that I am stuck on: according to my compiler `IDispatchPtr` is undefined. Which header file contains the definition? – Gary Jan 17 '23 at 20:36
  • Not a problem - found it in `#include ` – Gary Jan 17 '23 at 20:46
  • The compiler is complaining that `argument of type "_bstr_t *" is incompatible with parameter of type "LPOLESTR *"` in 'pDispatch->GetIDsOfNames'. I reverted to my previous code of `BSTR bstrFunction = SysAllocString(L"My_function");` but once again the same `type mismatch` error arises when I invoke the function. Any suggestions? Thanks! – Gary Jan 17 '23 at 20:54
  • @Gary I fixed the `BSTR` mismatch on `GetIDsOfNames()` – Remy Lebeau Jan 17 '23 at 23:00
  • Thanks @RemyLebeau. I'm still stuck on `type mismatch`. Regarding `SAFEARRAY`, should `vt constant` be `pparray` (and not `pvRecord` as I had it)? I looked here [https://www.quickmacros.com/help/Tables/IDP_VARIANT.html](https://www.quickmacros.com/help/Tables/IDP_VARIANT.html) but there is no name field for `VT_SAFEARRAY` ... which is why I mistakenly defaulted to `pvRecord`. – Gary Jan 18 '23 at 09:32
  • @Gary Sorry, my bad, I didn't even see the `VT_SAFEARRAY`, that works only in `TYPEDESC`. For `VARIANT` you have to use `VT_ARRAY` instead (`pvRecord` is used with `VT_RECORD`). I have updated my example. – Remy Lebeau Jan 18 '23 at 17:23
  • **you are an absolute genius!** This final step for the SAFEARRAY (`.vt = VT_R8 | VT_ARRAY | VT_BYREF`) nailed it. It now works 100 percent. A massive thank you for all the time you spent in assisting, and in coming back to address subsequent queries. – Gary Jan 18 '23 at 19:06