【问题标题】:Calling COM from C++ using CoCreateInstance and IDispatch, with numerous parameters in DISPPARAMS incl. SAFEARRAY使用 CoCreateInstance 和 IDispatch 从 C++ 调用 COM,DISPPARAMS 中包含大量参数,包括。安全阵列
【发布时间】:2023-01-18 07:10:56
【问题描述】:

我有一个公开函数的 COM 对象。我想将参数传递给此函数并接收返回值。我将 C++ 与 CoCreateInstance 一起使用。我收到的错误是hr = 0x8002000e : 参数数量无效.

我有理由相信我有正确的数字参数,我可以在 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);

我的代码摘要如下,它的工作原理如下所示:

#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;
}

我从https://stackoverflow.com/questions/65253592/idispatch-invoke-returns-type-mismatch 看到参数应该是相反的顺序。我试过了,即。使用 [4]、[3]、[2] 等而不是上面的 [0]、[1] 等,但这仍然会出错。

关于我可能哪里出错的任何建议?

顺便说一句,COM 来自 32 位 DLL,我正在将我的代码编译为 x86。

谢谢!!

【问题讨论】:

  • 在顶部,您传递的是 DISPATCH_METHOD 而不是 DISPATCH_PROPERTYGET。观察该函数在 IDL 中被标记为 propget
  • 您声称要传递 5 个命名参数,但未设置 DISPPARAMSrgdispidNamedArgs 成员。您可能希望将 cNamedArgs 设置为 0。
  • 参数必须以相反的顺序放入dispparams.rgvarg:最右边的参数在索引 0,最左边的在cArgs-1
  • [in, out] double* PdblPrice 必须作为VT_R8 | VT_BYREF 传递,double* 指针在VARIANT::pdblVal 成员中。与所有其他 [in, out] 参数相同。
  • @Gary 你需要使用这样的东西:DOUBLE value = 28.0; VARIANT dblPrice; dblPrice.vt = VT_R8 | VT_BYREF; dblPrice.pdblVal = &amp;value; dispparams.rgvarg[28] = dblPrice; 同样适用于所有其他VT_BYREF 值。

标签: c++ com invoke safearray idispatch


【解决方案1】:

您的代码有很多问题:

  • 缺乏错误处理。

  • 创建 COM 对象时,您无需获取其 IUnknown 即可立即查询 IDispatch。你可以直接获取它的IDispatch

  • bstrFunctiondispparams.rgvarg 内存泄漏。

  • 您正在创建 SAFEARRAYVT_R8 (double) 元素,但您正在使用 int* 指针来填充其值。您需要改用 double* 指针。

  • 您没有正确填充 DISPPARAMSVARIANTs。参数必须按照严格的顺序存储在DISPPARAMS 中。 VARIANTs 需要使用 VT_BYREF 标志,这意味着它们需要指向保存实际值的外部变量,而不是将值存储在 VARIANTs 本身中。

  • 使用错误的标志调用 IDispatch::Invoke()。您需要使用 DISPATCH_PROPERTYGET 而不是 DISPATCH_METHOD,因为该方法在 IDL 中被标记为 propget

  • 没有为 bolXP 参数正确使用 VARIANT_BOOL

  • 调用该方法后,您将打印出错误的字段 varResult。该方法在 IDL 中声明为返回 double,而不是 int

综上所述,尝试更像这样的事情:

#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_SAFEARRAY | 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
", varResult.dblVal);

    // Clean up
    SafeArrayDestroy(psa);

    return 0;
}

【讨论】:

  • 感谢@RemyLebeau 的详细回复。只是我坚持的一部分:根据我的编译器 IDispatchPtr 未定义。哪个头文件包含定义?
  • 没问题 - 在#include &lt;comdef.h&gt; 找到了它
  • 编译器抱怨“pDispatch->GetIDsOfNames”中的 argument of type "_bstr_t *" is incompatible with parameter of type "LPOLESTR *"。我恢复到以前的代码 BSTR bstrFunction = SysAllocString(L"My_function"); 但是当我调用该函数时再次出现相同的 type mismatch 错误。有什么建议么?谢谢!
  • @Gary 我修复了GetIDsOfNames() 上的BSTR 不匹配问题
猜你喜欢
  • 2011-03-24
  • 2014-04-12
  • 1970-01-01
  • 2011-12-25
  • 2013-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多