本文配套源码
一、前言
前段时间,由于工作比较忙,没有能及时地写作。其间收到了很多网友的来信询问和鼓励,在此一并表示感谢。咳......我也需要工作来养家糊口呀......
上回书介绍了两种方法来写自动化(IDispatch)接口的组件程序,一是用 MFC 方式编写“纯粹”的 IDispatch 接口;二是用 ATL 方式编写“双接口”的组件。
二、IDispatch 接口和双接口
使用者要想调用普通的 COM 组件功能,必须要加载这个组件的类型库(Type library)文件 tlb(比如在 VC 中使用 #import)。然而,在脚本程序中,由于脚本是被解释执行的,所以无法使用加载类型库的方式进行预编译。那么脚本解释器如何使用 COM 组件那?这就是自动化(IDispatch)组件大显身手的地方了。IDispatch 接口需要实现4个函数,调用者只通过这4个函数,就能实现调用自动化组件中所有的函数。这4个函数功能如下:
HRESULT GetTypeInfoCount( [out] UINT * pctinfo) |
组件中提供几个类型库?当然一般都是一个啦。 但如果你在一个组件中实现了多个 IDispatch 接口,那就不一定啦(注1) |
HRESULT GetTypeInfo( [in] UINT iTInfo, [in] LCID lcid, [out] ITypeInfo ** ppTInfo) |
调用者通过该函数取得他想要的类型库。 幸好,在 99% 的情况下,我们都不用关心这两个函数的实现,因为 MFC/ATL 都帮我们完成了默认的一个实现,如果是自己完成函数代码,甚至可以直接返回 E_NOTIMPL 表示没有实现。(注2) |
HRESULT GetIDsOfNames( [in] REFIID riid, [in,size_is(cNames)] LPOLESTR * rgszNames, [in] UINT cNames, [in] LCID lcid, [out,size_is(cNames)] DISPID * rgDispId) |
根据函数名称取得函数序号,为调用 Invoke() 做准备。 所谓函数序号,大家去观察双接口 IDL 文件和 MFC 的 ODL 文件,每一个函数和属性都会有 [id(序号)....] 这样的描述。 |
HRESULT Invoke( [in] DISPID dispIdMember, [in] REFIID riid, [in] LCID lcid, [in] WORD wFlags, [in,out] DISPPARAMS * pDispParams, [out] VARIANT * pVarResult, [out] EXCEPINFO * pExcepInfo, [out] UINT * puArgErr) |
根据序号,执行函数。 使用 MFC/ATL 写的组件程序,我们也不必关心这个函数的实现。如果是自己写代码,则该函数类似如下实现: switch(dispIdMember) { case 1: .....; break; case 2: .....; break; .... } 其实,就是根据序号进行分支调用啦。(注3) |
从 Invoke() 函数的实现就可以看出,使用 IDispatch 接口的程序,其执行效率是比较低的。ATL 从效率出发,实现了一种叫“双接口(dual)”的接口模式。下面我们来看看,到底什么是双接口:
图一、双接口(dual) 结构示意图