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

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

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

  • 最好将IDispatch方法调用委托给某个ITypeInfo 。 它是否正确?
  • 如果是这样,如何获得ITypeInfo给自己? LoadTypeLib()和family(后面是查看ITypeLib::GetTypeInfo() )?
  • 如果没有,它是如何正确实施的? 与高质量文档和自包含示例的链接非常有用。

LoadTypeLib()方法似乎适合COM 客户端访问某些库的类型信息,而不适用于尝试内省自身的COM服务器。 我对么?

如果在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(this), dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); } } 

我已经使用类似的方法为MSHTML DOM对象创建一个脚本可调用包装器,以绕过脚本安全限制。

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

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

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

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

然后你需要支持的是GetIDsOfNamesInvoke 。 它本质上只是一个很大的查找表。

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

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

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

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

你可以做的是使用类型库 。

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

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