COM组件设计与应用(九) IDispatch 接口 for vc6.0
下载源代码一、前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性。 如果你使用 vc6.0 的开发环境,请继续阅读。 如果你使用 vc.net 2003,请阅读下一回。
二、IDispatch接口 如果是编译型语言,那么我们可以让编译器在编译的时候装载类型库,也就是装载接口的描述。在第七回文章当中,我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后,编译器就知道应该如何编译接口函数的调用了---这叫“前绑定”。但是,如果想在脚本语言中使用组件,问题就大了,因为脚本语言是解释执行的,它执行的时候不会知道具体的函数地址,怎么办?自动化接口就为此诞生了---“后绑定”。 自动化组件,其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数,解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下:(注1)
[ object, uuid(00020400-0000-0000-C000-000000000046), // IDispatch 接口的 IID = IID_IDispatch pointer_default(unique) ] interface IDispatch : IUnknown { typedef [unique] IDispatch * LPDISPATCH; // 转定义 IDispatch * 为 LPDISPATCH HRESULT GetTypeInfoCount([out] UINT * pctinfo); // 有关类型库的这两个函数,咱们以后再说 HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo); HRESULT GetIDsOfNames( // 根据函数名字,取得函数序号(DISPID) [in] REFIID riid, [in, size_is(cNames)] LPOLESTR * rgszNames, [in] UINT cNames, [in] LCID lcid, [out, size_is(cNames)] DISPID * rgDispId ); [local] // 本地版函数 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 ); [call_as(Invoke)] // 远程版函数 HRESULT RemoteInvoke( [in] DISPID dispIdMember, [in] REFIID riid, [in] LCID lcid, [in] DWORD dwFlags, [in] DISPPARAMS * pDispParams, [out] VARIANT * pVarResult, [out] EXCEPINFO * pExcepInfo, [out] UINT * pArgErr, [in] UINT cVarRef, [in, size_is(cVarRef)] UINT * rgVarRefIdx, [in, out, size_is(cVarRef)] VARIANTARG * rgVarRef ); }以上 IDispatch 接口函数的讲解,我们留到后回中进行介绍。如何在组件程序中实现这些函数那?还好,还好,就象 IUnknown 一样,MFC 和 ATL 都帮我们已经完成了。本回我们着重介绍组件的编写,下回则介绍组件的调用方法。 三、用 MFC 实现自动化组件 我写的这整个系列文章---《COM 组件设计与应用》,多是用 ATL 写组件程序,但由于自动化非常有用,在后续的文章中,还要给大家介绍组件的“事件”功能,还要介绍如何在 MFC 的程序中象 WORD 一样支持“宏”的功能。这些都要用到 MFC,所以就给读者唠一唠啦:-) 3-1:建立一个工作区(Workspace) 3-2:建立一个 MFC DLL 工程(Project),工程名称为“Simple5”
![](https://i-blog.csdnimg.cn/blog_migrate/0c00d04b0aca6f392fce3a824cace832.png)
![](https://i-blog.csdnimg.cn/blog_migrate/54aa1fc0a60d31149b00ff792b842741.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8449ca09ac445292e9bfcf9967199a3f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3613bf29e440dfb9e75da5efc8cbf6b4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7807ebce0cba7732399065241d40e9f9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/acfdbae48757083dfe568cb23fe946cc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b041f0f3d1a19dafd063b1597643ed3a.png)
long CDispSimple::Add(long n1, long n2) { return n1 + n2; } BSTR CDispSimple::Upper(LPCTSTR str) { CString strResult(str); strResult.MakeUpper(); return strResult.AllocSysString(); }3-10:编译注册 如果上面的操作由于疏忽而发生了错误,那么你可以手工进行改正。 其一、步骤<3-6>的对话窗中有“Delete”操作; 其二、你可以打开 ODL 文件(注2)进行修改,修改时要特别小心函数的声明中,有一个[id(n)] 的函数序号,可不要乱了; 其三、同步修改 H/CPP 中的函数声明和函数体; 其四、在CPP文件中,根据情况也要修改 BEGIN_DISPATCH_MAP/END_DISPATCH_MAP()函数影射宏。 正确编译后,MFC不象ATL那样会自动注册。你需要手工执行 regsvr32.exe 进行注册,或者执行菜单“Tools/Register control” 四、用 ATL 实现双接口组件(操作方法和步骤,请参考 《 COM 组件设计与应用(五)》) 4-1:建立一个 ATL 工程(Project),工程名称为“Simple6” 4-2:按默认进行。选择 DLL 类型、不合并代理和存根代码、不支持MFC、不支持MTS 4-3:New Atl Object... 选择Simple Object 4-4:输入名称和属性,属性按默认进行,也就是 dual(双接口)方式(注3)
![](https://i-blog.csdnimg.cn/blog_migrate/ef5634a1e647101387db202b797ea9ac.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ab0c8cc17ac36f32f89ffa134c60d7f2.png)
STDMETHODIMP CDispSimple::Add(VARIANT v1, VARIANT v2, VARIANT *pVal) { ::VariantInit( pVal ); // 永远初始化返回值是个好习惯 CComVariant v_1( v1 ); CComVariant v_2( v2 ); if((v1.vt & VT_I4) && (v2.vt & VT_I4) ) // 如果都是整数类型 { // 这里比较没有使用 == ,而使用了运算符 & ,你知道这是为什么吗? v_1.ChangeType( VT_I4 ); // 转换为整数 v_2.ChangeType( VT_I4 ); // 转换为整数 pVal->vt = VT_I4; pVal->lVal = v_1.lVal + v_2.lVal; // 加法 } else { v_1.ChangeType( VT_BSTR ); // 转换为字符串 v_2.ChangeType( VT_BSTR ); // 转换为字符串 CComBSTR bstr( v_1.bstrVal ); bstr.AppendBSTR( v_2.bstrVal ); // 字符串连接 pVal->vt = VT_BSTR; pVal->bstrVal = bstr.Detach(); } return S_OK; } STDMETHODIMP CDispSimple::Upper(BSTR str, BSTR *pVal) { *pVal = NULL; // 永远初始化返回值是个好习惯 CComBSTR s(str); s.ToUpper(); // 转换为大写 *pVal = s.Copy(); return S_OK; }刚才卖的关子,现在开始揭密了......加法函数Add()不使用long类型,而使用VARIANT的好处是:函数内部动态判断参数类型,如果是整数则进行整数加法,如果是字符串,则进行字符串加法(字符串加法就是字符串连接哈)。也就是说,如果参数是VARIANT,那么我们就可以实现函数的可变参数类型呀。怪怪个咙,真爽! 五、脚本中调用举例 打开“记事本”程序,输入脚本程序,保存为 xxx.vbs 文件。然后在资源管理器里就可以双击运行啦。
![](https://i-blog.csdnimg.cn/blog_migrate/94cbd4b48f5484b98d393fa4f88b3024.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2cc4de3a0156608e639e5ff5b22ebae5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/54d7942a30353a3a1ce4c0defe677c95.png)
![](https://i-blog.csdnimg.cn/blog_migrate/15ddeddbf622a1d94c48efd667278559.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ced4d1eed5a9b25415031d7c40beb7cc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c6ad86ea40087f6f0266105acd84e59d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9a346fa1a92461730d72dc1a8ad5634a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a535e3e20ae6ae31f66e5e40ceae7683.png)
![](https://i-blog.csdnimg.cn/blog_migrate/997eb680131547fc9f1b37a2377d9727.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1238032919289dd35c3a61a61d9ca8e7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/de4df0bab841394129971ebbfeb24fc1.png)
注1:以后我们描述接口函数,都采用 IDL 的形式了。 注2:ODL 文件和 IDL 类似,是MFC专门为自动化而描述的接口文件 注3:双接口,是支持 IDispatch 接口的一种特殊接口方式,后面马上就要讲啦 注4:VBA 是专门开发 Office 的一种语言---Visual Basic for Application