在没有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? 基本上你通过以下方式得到它:
- 编写一个IDL文件,将您的接口声明为双接口。 它必须是一个双接口,因为
ITypeInfo
实现知道调用哪个函数 – 它不能直接在你的类上调用C ++函数,因为C ++没有reflection,因为它是语言中立的。 因此,它只能将Invoke
调用委托给类型库中声明的另一个方法。 - 将IDL编译为头文件并在构建过程中键入库
- 从IDL生成的头文件定义了实现类必须inheritance的接口。 一旦你实施了所有方法,你就可以了。 (对于开发,首先让它们全部返回
E_NOTIMPL
然后逐个实现它们) - 将类型库安装到目标目录,或者作为EXE / DLL中的资源。 它需要通过调用
RegisterTypeLib
。 如果它作为资源嵌入,则应从DllRegisterServer
实现中调用它。 - 使用
LoadTypeLib
在创建对象的第一个实例时加载类型库。 这为您提供了一个ITypeLib
- 使用
GetTypeInfoOfGuid
获取所需的ITypeInfo。
实现IDispatch可能很容易或很难。 (假设你不能使用ATL)。
简单的方法是不支持TypeInfo( GetTypeInfoCount
返回0, GetTypeInfoCount
返回E_NOTIMPL
。没有人应该调用它。)。
然后你需要支持的是GetIDsOfNames
和Invoke
。 它本质上只是一个很大的查找表。
对于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实现调用中,因为它们非常相似。