【问题标题】:Enumerate COM object (IDispatch) methods using ATL?使用 ATL 枚举 COM 对象 (IDispatch) 方法?
【发布时间】:2011-01-07 21:52:28
【问题描述】:

使用 ATL (VS2008) 如何枚举给定 IDispatch 接口 (IDispatch*) 上可用的可用方法?我需要搜索一个具有特定名称的方法,一旦有了DISPID,就调用该方法(我知道该方法采用的参数。)理想情况下,我想使用智能 COM 指针(CComPtr<>)执行此操作.

这可能吗?

【问题讨论】:

标签: c++ com atl


【解决方案1】:

您可以枚举IDispatch 通过类型信息公开的方法。获取类型信息有两种方式:

很遗憾,IDispatch 实现没有义务提供有关其实现的方法和属性的类型信息。

但是,如果确实如此,则基本枚举涉及调用 ITypeInfo::GetTypeAttr 以获取接口的 TYPEATTR 并查看已实现方法 (cFuncs) 和变量 (cVars) 的数量并循环这些并致电ITypeInfo::GetFuncDesc()ITypeInfo::GetVarDesc()。当然,您还需要处理更多细节,我可以在这里列出,但这应该是您探索的一个很好的起点。

这是一个不错的article explaining the process in more details,带有 VB.Net 中的代码。

【讨论】:

  • 好东西。感谢您添加此内容。
  • @Franci,当属性是数组时,返回的 VARDESC 有一个 varkind=IDispatch。你怎么知道一个属性是否是一个数组,如果是一个数组 - 我如何访问它的成员?调用 Invoke 获取数组时,结果为 IDispatch。此 IDispatch 不支持“Item”或“Length”或任何类似属性。
  • @Uri - 请注意,属性不是字段,应通过GetFuncDesc() 进行检查,它会为您提供FUNCDESC,您必须从那里转到elemdescFunc(用于返回)或@ 987654340@(用于参数)。数组通常作为 out 参数返回,因此您应该检查后者。在任何情况下,这两个都给出ELEMDESC,你应该检查tdesk,它返回给你一个TYPEDESC,它基于VARTYPE vt可能实际上是一个ARRAYDESC。如果是这样,你有一个SAFEARRAY
  • @Uri - 如果事实证明它不是 SAFEARRAY,则该属性很可能返回一个 IDispatch 指针,指向实现类似数组功能的对象作为一个集合。您应该向 IDispatch 询问其类型信息并检查该信息以找出访问集合成员的正确方法。
  • @Uri - 如果您的数组来自 JScript,则它不是 SAFEARRAY,而是 JScript Array 对象的 IDIspatch。下面是对 VBScript 数组(即 SAFEARRAYS)和 JScript 数组之间区别的描述 - blogs.msdn.com/b/ericlippert/archive/2003/09/22/53061.aspx
【解决方案2】:

这是一些执行枚举的代码(它将 [Dispatch ID]-[Method Name] 对插入到地图中,但这很容易更改)。

///
/// \brief Returns a map of [DispId, Method Name] for the passed-in IDispatch object
///
HRESULT COMTools::GetIDispatchMethods(_In_ IDispatch * pDisp,
                                      _Out_ std::map<long, std::wstring> & methodsMap)
{
    HRESULT hr = S_OK;

    CComPtr<IDispatch> spDisp(pDisp);
    if(!spDisp)
        return E_INVALIDARG;

    CComPtr<ITypeInfo> spTypeInfo;
    hr = spDisp->GetTypeInfo(0, 0, &spTypeInfo);
    if(SUCCEEDED(hr) && spTypeInfo)
    {
        TYPEATTR *pTatt = nullptr;
        hr = spTypeInfo->GetTypeAttr(&pTatt);
        if(SUCCEEDED(hr) && pTatt)
        {
            FUNCDESC * fd = nullptr;
            for(int i = 0; i < pTatt->cFuncs; ++i)
            {
                hr = spTypeInfo->GetFuncDesc(i, &fd);
                if(SUCCEEDED(hr) && fd)
                {
                    CComBSTR funcName;
                    spTypeInfo->GetDocumentation(fd->memid, &funcName, nullptr, nullptr, nullptr);
                    if(funcName.Length()>0)
                    {
                        methodsMap[fd->memid] = funcName;
                    }

                    spTypeInfo->ReleaseFuncDesc(fd);
                }
            }

            spTypeInfo->ReleaseTypeAttr(pTatt);
        }
    }

    return hr;

}

【讨论】:

    【解决方案3】:

    除非对象实现 IDispatchEx,否则无法枚举所有可用方法。

    但是,如果您知道要调用的方法的名称,则可以使用 GetIDsOfNames 将该名称映射到正确的 DISPID。

    HRESULT hr;
    CComPtr<IDispatch> dispatch;
    DISPID dispid;
    WCHAR *member = "YOUR-FUNCTION-NAME-HERE";
    DISPPARAMS* dispparams;
    
    // Get your pointer to the IDispatch interface on the object here.  Also setup your params in dispparams.
    
    hr = dispatch->GetIDsOfNames(IID_NULL, &member, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if (SUCCEEDED(hr)) {
      hr = dispatch->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, dispparams, &varResult, NULL, NULL);
    }
    

    编辑:为了完整起见,我怀疑有一种方法可以询问您从 IDispatch::GetTypeInfo 获得的 ITypeInfo2 接口(假设对象有一个类型库)以获取方法列表,但我还没有完成它。请参阅其他答案。

    【讨论】:

    • 太棒了!正是我需要的。非常感谢。
    • 我相信我在我的回答中表达了您在评论中提出的每一点。请仔细阅读一遍。此外,发布者只是希望能够调用一个他已经知道名称的方法。所以我的回答为他真正想做的事情提供了解决方案,不一定是他问的问题。这就是为什么,我怀疑它被标记为正确答案。最后,请冷静。只有 1 和 0。
    猜你喜欢
    • 2013-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-15
    • 2010-12-11
    • 2010-11-09
    • 2021-03-08
    • 2012-06-26
    相关资源
    最近更新 更多