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.
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 VARIANT
s correctly. The parameters have to be stored in the DISPPARAMS
in revere order. And the VARIANT
s 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 VARIANT
s 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;
}