问题 在没有ATL的情况下实现COM IDispatch


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

一如既往,文档非常糟糕。到目前为止我所读的内容如下:

  • 这是更好的做法 委托 IDispatch 方法调用一些 ITypeInfo。它是否正确?
  • 如果是这样,如何获得 ITypeInfo 对自己? LoadTypeLib() 和家人(然后看着 ITypeLib::GetTypeInfo())?
  • 如果没有,它是如何正确实施的?与高质量文档和自包含示例的链接非常有用。

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


4579
2017-10-08 16:39


起源

通常,您通过向Microsoft支付IDLatch来为您提供ATL副本。文件的历史可以追溯到1999年ATL不可用的时候。现在,ATL是你如何做到的。期。 (我已经实现了IDispatch。我不会推荐这种体验,这是一项艰苦的工作和大量的打字。) - Ben
@Ben:嗯,我同意你的观点,这可能是最便宜的方式。那些该死的巨魔技术。 (我撒谎:我确实可以访问ATL,但不能访问我的工作站,我只是想知道我是否可以在没有它的情况下相处。但是,嘿,不要告诉任何人,好吗?) - alecov
我发现“内部COM +基础服务”非常有用“回到何时” - 它有一章关于自动化,其中包括实现 IDispatch。 - Georg Fritzsche
嘿,也许Qt有办法做到这一点。我对Qt了解多少?不是很多。 - Ben
@GeorgFritzsche:太棒了, thrysoee.dk/InsideCOM+, 谢谢。通过浏览 IDispatch 部分给我一个实现使用 ITypeInfo 和 LoadTypeLib() 正如我所提到的那样。 @Ben:我担心Qt没有太多代码可以解决这个问题。 - alecov


答案:


如果接口在IDL中正确定义并编译成类型库,则实现 IDispatch 通过类型库 ITypeInfo 这是非常可行的,因为它主要是委托。有趣的是 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); 
    }
}

我使用了类似的方法来创建 MSHTML DOM对象的脚本可调用包装器 绕过脚本安全限制。

那么从哪里获得ITypeInfo?基本上你通过以下方式得到它:

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

4
2017-10-08 22:13



+1这样你就是这样做的...... - Ben
你确定在这种情况下ITypeInfo :: Invoke不会调用实例的IDispatch :: Invoke(这是鸡和蛋的问题)吗? - Simon Mourier
@SimonMourier,我非常肯定。它使用双接口的二进制v-table布局,并且它知道前7个条目,从 AddRef。 - Noseratio
@Noseratio:谢谢你这个非常慷慨的解释。我还没有足够的时间来解决这个问题,但如果一切顺利,我会告诉你的。 - alecov
很长一段时间后(因为我离开了这个问题,现在我又回到了它),我可以说这个方法有效 完美。非常非常感谢Noseratio!一些观察:1。我找不到任何相关的东西 IDualInterface,我只是用过 IDispatch 代替; 2.有关注册的MSDN文档是虚假的 stackoverflow.com/a/285060/259543 给出了正确的方法。特别是你 将 需要 HKCR\Software\CLSID\<CLSID>\Programmable 您的组件出现在例如Microsoft Excel。我甚至打算写一个 正确 关于如何做到这一点的文章。 - alecov


实现IDispatch可能很容易或很难。 (假设你不能使用ATL)。

简单的方法是不支持TypeInfo(从0开始返回0) GetTypeInfoCount 和 E_NOTIMPL 从 GetTypeInfo。没人应该叫它。)

那你需要支持的就是 GetIDsOfNames 和 Invoke。它本质上只是一个很大的查找表。

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

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

困难的方法是使用ITypeInfo以及所有这些。我从来没有这样做过,也不愿意。 ATL让它变得简单,所以只需使用ATL即可。

如果采取艰难的方式,祝你好运。


6
2017-10-08 16:46



谢谢本。当你还在这里时,为什么要使用它 ITypeInfo 比手工实施更难?我乍一看似乎更容易,虽然我认为以这种方式实施它是不合适的。 - alecov
使用ITypeInfo的困难主要在于您必须花时间研究如何使用它。有时候,通过艰苦的方式完成一项小工作会更容易,因为没有学习曲线。如果我在做 许多 IDispatch实现我可能会发现值得找出如何使用它... - Ben


你能做的就是用a 类型库

如果你有一个,这是你不必做的一件事。如果你没有,那么你可以使用 MIDL编译器。这是Platform SDK附带的免费工具。当然在这种情况下,这意味着你必须编写一个IDL文件(这可能是很多工作,但你只需要定义你想要的东西)。根据您要定位的COM对象的类型,SDK中可能已经提供了IDL文件。准备好IDL之后,就可以编译它了 获取TLB文件

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


3
2017-10-08 17:19