【问题标题】:Implement COM IDispatch without ATL在没有 ATL 的情况下实现 COM IDispatch
【发布时间】:2013-10-15 17:11:50
【问题描述】:

我正在编写一个 Excel RTD 服务器实现,但我被困在实现 IDispatch 的 coclass 的样板上。我无法访问 ATL,但我使用的是 ActiveQt,尽管我也对如何在原始 C 或 C++ 中执行此操作感兴趣。如何在 COM 服务器中正确实现IDispatch 方法?

文档一如既往地糟糕透顶。到目前为止我读过的内容:

LoadTypeLib() 方法似乎适合 COM 客户端 获取某些库的类型信息,而不适合尝试自省的 COM 服务器。我说的对吗?

【问题讨论】:

  • 通常,您通过向 Microsoft 支付 ATL 副本来实施 IDispatch。文档可以追溯到大约 1999 年,当时 ATL 不可用。现在,ATL 就是您的做法。时期。 (我已经实现了 IDispatch。我不推荐这种体验,它很辛苦,而且打字很多。)
  • @Ben:嗯,我同意你的观点,这可能是最便宜的方式。那些该死的巨魔技术。 (我撒谎了:我确实可以访问 ATL,但不能在我的工作站上,我只是想知道如果没有它我是否能过得去。但是,嘿,不要告诉任何人,好吗?)
  • 我发现“内部 COM+ 基础服务”非常有用,“回到什么时候”——它有一个关于自动化的章节,其中涵盖了实现 IDispatch
  • 嘿,也许 Qt 有办法做到这一点。我对 Qt 了解多少?不是很多。
  • @GeorgFritzsche:太棒了,thrysoee.dk/InsideCOM+,谢谢。正如我所提到的,浏览IDispatch 部分给了我一个使用ITypeInfoLoadTypeLib() 的实现。 @Ben:恐怕 Qt 没有太多代码来处理这个问题。

标签: c++ c qt com


【解决方案1】:

实施 IDispatch 可能容易或困难。 (假设您不能使用 ATL)。

简单的方法是不支持 TypeInfo(从GetTypeInfoCountE_NOTIMPLGetTypeInfo 返回 0。没有人应该调用它。)。

那么你只需要支持GetIDsOfNamesInvoke。它本质上只是一个大查找表。

对于GetIDsOfNames,如果cNames != 1,则返回DISP_E_UNKNOWNNAME。你不会支持参数名称。然后,您只需在名称到 ID 的映射中查找 rgszNames[0]

最后,实现 Invoke。忽略除 pDispParams 和 pVarResult 之外的所有内容。使用 VariantChangeType 将参数强制为您期望的类型,并传递给您的实现。设置返回值并返回。完成。

困难的方法是使用 ITypeInfo 和所有这些。我从来没有做过,也不会。 ATL 让一切变得简单,所以只需使用 ATL。

如果选择困难,祝你好运。

【讨论】:

  • 谢谢本。当您还在这里时,为什么使用ITypeInfo 比手动实现更难?乍一看,这似乎更容易,尽管我认为以这种方式实现它是不合适的。
  • 使用 ITypeInfo 的困难主要在于您必须花时间研究如何使用它。有时,因为没有学习曲线,所以以艰难的方式完成一项小工作会更容易。如果我正在执行许多 IDispatch 实现,我可能会发现了解如何使用它是值得的......
  • 我个人认为不处理 IDL 和加载类型库的另一个因素是我还没有找到从一些嵌入式 TLB 文件(嵌入在我构建的程序中)加载库的方法,因为与在应用程序中携带构建 TLB 文件相反,因为它是在运行时加载的。所以我只是使用这个答案中提出的方法。如果有办法从嵌入式 TLB(由 MIDL 编译器或其他编译器编译)“自动”加载类型库,我可能会使用它。
【解决方案2】:

您可以使用Type Library

如果你有一个,那是你不必做的一件事。如果您没有,则可以使用MIDL compiler 创建一个。这是 Platform SDK 附带的免费工具。当然,在这种情况下,这意味着您将不得不编写一个 IDL 文件(这可能是很多工作,但您只需要定义您想要的内容)。根据您所针对的 COM 对象的类型,IDL 文件可能已经在 SDK 中可用。一旦准备好 IDL,就可以编译它并 get back a TLB file

获得该 TLB 文件后,您可以使用 LoadTypeLib function 加载它。一旦你有一个ITypeLib 引用,你就可以加载你需要的ITypeInfo(可能不止一次),并且基本上将 IDispatch 调用(GetIDsOfNames 等)路由到 ITypeInfo 实现调用中,因为它们非常相似。

【讨论】:

    【解决方案3】:

    如果接口在 IDL 中正确定义并编译到类型库中,通过类型库的 ITypeInfo 实现 IDispatch 是非常可行的,因为它主要是委托。有趣的部分是 ITypeInfo::Invoke,它依赖于正确的 C++ v-table 布局:

    public class CComClass: public IDualInterface
    {
        // ...
    
        // implementing IDualInterface
    
        ITypeInfo* m_pTypeInfo // can be obtained via ITypeLib::GetTypeInfoOfGuid for the GUID of IDualInterface
    
        // IDispatch
        STDMETHODIMP GetTypeInfoCount(UINT* pctinfo)
        {
            *pctinfo = 1;
            return S_OK;
        }
    
        STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
        {
            if (0 != itinfo)
                return E_INVALIDARG;
            (*pptinfo = m_pTypeInfo)->AddRef();
            return S_OK;
        }
    
        STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
        {
            return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgdispid);
        }
    
        STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
        {
            return m_pTypeInfo->Invoke(static_cast<IDualInterface*>(this), dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); 
        }
    }
    

    我使用了类似的方法来创建 a script-callable wrapper for MSHTML DOM objects 以绕过脚本安全限制。

    那么你从哪里得到 ITypeInfo 呢?基本上你可以通过:

    1. 编写一个 IDL 文件,将您的接口声明为 接口。它必须是双接口,因为 ITypeInfo 实现就是这样知道要调用哪个函数的——它不能直接在你的类上调用 C++ 函数,因为 C++ 没有反射,而且它是语言中立的。因此它只能将Invoke 调用委托给类型库中声明的另一个方法。
    2. 在构建过程中将 IDL 编译为头文件和类型库
    3. 从 IDL 生成的头文件定义接口,您的实现类必须从该接口继承。一旦你实现了所有的方法,你就可以开始了。 (对于开发,首先让它们都返回E_NOTIMPL,然后一一实现)
    4. 将类型库安装到目标目录或作为 EXE/DLL 中的资源。需要通过调用RegisterTypeLib 进行注册。如果它作为资源嵌入,您应该从您的 DllRegisterServer 实现中调用它。
    5. 在创建对象的第一个实例时加载类型库,使用LoadTypeLib。这会给你一个ITypeLib
    6. 使用 GetTypeInfoOfGuid 获取所需的 ITypeInfo。

    【讨论】:

    • 你确定在这种情况下 ITypeInfo::Invoke 不会调用实例的 IDispatch::Invoke 吗(那是先有鸡还是先有蛋的问题)?
    • @SimonMourier,我很确定。它使用双界面的二进制 v-table 布局,它知道前 7 个条目,从 AddRef 开始。
    • 我对@9​​87654322@ 此处的文档的理解有所不同:“调用时,ITypeInfo::Invoke 在指定实例上执行 IDispatch::Invoke 参数描述的操作。”
    • @Noseratio:感谢您非常慷慨的解释。我还没有足够的时间来处理这个问题,但如果一切顺利的话,我会通知你的。
    • 过了很久(自从我离开这个问题,现在我又回到了它),我可以说这个方法完美地工作。非常非常感谢诺塞拉蒂奥!一些观察: 1.我找不到与IDualInterface相关的任何东西,我只是用IDispatch代替; 2. MSDN 关于注册的文档是假的,stackoverflow.com/a/285060/259543 给出了正确的方法。特别是,您需要HKCR\Software\CLSID\&lt;CLSID&gt;\Programmable 才能让您的组件出现在例如微软 Excel。我什至打算写一篇关于如何做到这一点的正确文章。
    猜你喜欢
    • 2021-06-12
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-27
    • 2011-12-30
    • 1970-01-01
    相关资源
    最近更新 更多