D语言中国主页  D语言编辑器SciTE4D   DWin库 D语言官方网站
D语言编译器1.x最新版 OpenSource   Tango   webnews  Wiki

查看完整版本: COM组件设计与应用11 IDispatch及双接口的调用

yidabu 2007-5-17 09:23

COM组件设计与应用11 IDispatch及双接口的调用

COM组件设计与应用11 IDispatch及双接口的调用知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070517点击下面网址查看原文:http://bbs.d-programming-language-china.org/thread-597-1.html        by:        杨老师        下载源代码        http://www.vckbase.com/code/downcode.asp?id=2744前言        前段时间,由于工作比较忙,没有能及时地写作。其间收到了很多网友的来信询问和鼓励,在此一并表示感谢。咳......我也需要工作来养家糊口呀......        上回书介绍了两种方法来写自动化(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) 结构示意图        从上图中可以看出,所谓双接口,其实是在一个 VTAB 的虚函数表中容纳了三个接口(因为任何接口都是从 IUnknown 派生的,所以就不强调 IUnknown 了,叫做双接口)。我们如果从任意一个接口中调用 QueryInterface()得到另外的接口指针的话,其实,得到的指针地址都是同一个。双接口有什么好处那?答:好呀,多好呀,特别好呀......( 本文出处: http://bbs.d-programming-language-china.org/thread-597-1.html )                        使用方式        因为        所以                        脚本语言使用组件        解释器只认识 IDispatch        接口        可以调用,但执行效率最低                        编译型语言使用组件        它认识 IDispatch        接口        可以调用,执行效率比较低                        编译型语言使用组件        它装载类型库后,就认识了 Ixxx        接口        可以直接调用 Ixxx        函数,效率最高啦                                结论                双接口,既满足脚本语言的使用方便性,又满足编译型语言的使用高效性。        于是,我们写的所有的 COM 组件接口,都用双接口实现吗?        错!否!NO!        如果不是明确非要支持脚本的调用,则最好不要使用双接口,因为:                                如果所有函数都放在一个双接口中,那么层次、结构、分类不清                                如果使用多个双接口,则会产生其它问题(注4)                        双接口、IDispatch接口只支持自动化的参数类型,使用受到限制,某些情况下很不方便喽                        还有很多弊病呦,不过现在我想不起来喽......                使用方法        如果你的开发环境是 vc6.0,那么我们使用第九回中的Simple6组件为例,快去下载呀......        如果你的开发环境是 vc.net 2003,那么用第十回中的Simple8组件为例,快去下载呀......        嘿嘿,其实不下载也没有关系,因为你只要下载本回的示例程序,里面已经包含了所需的组件。但使用前不要忘了去注册呀:regsvr32.exe simple6.dll 或 regsvr32.exe simple8.dll (注意别忘了输入组件的安装目录)。注册成功后,就可以使用了,使用方法有:                        示例程序        自动化组件的使用方式        简要说明                        示例0        在脚本中调用        在第九回/第十回中,已经做了介绍                        示例1        使用 API 方式调用        揭示 IDispatch        的调用原理,但傻子才去这么使用那,会累死了                        示例2        使用         CComDispatchDriver        的智能指针包装类        比直接使用 API 方式要简单多啦,这个不错!                        示例3        使用 MFC 装载类型库的包装方式        简单!好用!常用!但它本质上是使用 IDispatch        接口,所以执行效率稍差                        示例4        使用 #import        方式加载类型库方式        #import 方式使用组件,咱们在第七回中讲过啦。常用!对双接口组件,直接调用自定义接口函数,不再经过        IDispatch,因此执行效率最高啦                        示例x        vb、java、c#、bcb、delphi.......        反正我不会,自己去请教高人去吧 :-(                        示例一、IDispatch 调用原理篇        QUOTE:                                void demo()                {                    ::CoInitialize( NULL );        // COM 初始化                                    CLSID clsid;                // 通过 ProgID 得到 CLSID                    HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );                    ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有注册组件                                    IDispatch * pDisp = NULL;    // 由 CLSID 启动组件,并得到 IDispatch 指针                    hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp );                    ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有初始化 COM                                    LPOLESTR pwFunName = L"Add";    // 准备取得 Add 函数的序号 DispID                    DISPID dispID;                    // 取得的序号,准备保存到这里                    hr = pDisp->GetIDsOfNames(        // 根据函数名,取得序号的函数                    IID_NULL,                    &pwFunName,                    // 函数名称的数组                    1,                            // 函数名称数组中的元素个数                    LOCALE_SYSTEM_DEFAULT,        // 使用系统默认的语言环境                    &dispID );                    // 返回值                    ASSERT( SUCCEEDED( hr ) );        // 如果失败,说明组件根本就没有 ADD 函数                                    VARIANTARG v[2];                    // 调用 Add(1,2) 函数所需要的参数                    v[0].vt = VT_I4;    v[0].lVal = 2;    // 第二个参数,整数2                    v[1].vt = VT_I4;    v[1].lVal = 1;    // 第一个参数,整数1                                    DISPPARAMS dispParams = { v, NULL, 2, 0 };    // 把参数包装在这个结构中                    VARIANT vResult;            // 函数返回的计算结果                                    hr = pDisp->Invoke(            // 调用函数                    dispID,                    // 函数由 dispID 指定                    IID_NULL,                    LOCALE_SYSTEM_DEFAULT,    // 使用系统默认的语言环境                    DISPATCH_METHOD,        // 调用的是方法,不是属性                    &dispParams,            // 参数                    &vResult,                // 返回值                    NULL,                    // 不考虑异常处理                    NULL);                    // 不考虑错误处理                    ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明参数传递错误                                    CString str;            // 显示一下结果                    str.Format("1 + 2 = %d", vResult.lVal );                    AfxMessageBox( str );                                    pDisp->Release();        // 释放接口指针                    ::CoUninitialize();        // 释放 COM                }                示例二、CComDispatchDriver 智能指针包装类的使用方法        QUOTE:                                void demo()                {                    // 已经进行过了 COM 初始化                                    CLSID clsid;                // 通过 ProgID 取得组件的 CLSID                    HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );                    ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有注册组件                                    CComPtr < IUnknown > spUnk;    // 由 CLSID 启动组件,并取得 IUnknown 指针                    hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&spUnk );                    ASSERT( SUCCEEDED( hr ) );                                    CComDispatchDriver spDisp( spUnk );    // 构造只能指针                    CComVariant v1(1), v2(2), vResult;    // 参数                    hr = spDisp.Invoke2(    // 调用2个参数的函数                    L"Add",                // 函数名是 Add                    &v1,                // 第一个参数,值为整数1                    &v2,                // 第二个参数,值为整数2                    &vResult);            // 返回值                    ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明或者没有 ADD 函数,或者参数错误                                    CString str;            // 显示一下结果                    str.Format("1 + 2 = %d", vResult.lVal );                    AfxMessageBox( str );                }                示例程序中使用了 Invoke2()函数,其实你根据不同的函数,还可以使用 Invoke0()、Invoke1()、InvokeN()、PutProperty()、GetProperty()......等等等,的确很方便。        示例三、加载类型库,产生包装类来使用        这个方法使用更简单一些,如果你观察 MFC 帮你产生的包装类的实现,你就会发现,其实它调用的是 IDispatch 接口函数。使用 vc6.0 的朋友,步骤如下:        1、建立一个 MFC 的应用程序        2、开启 ClassWizard,执行 Add Class,选择 From a type library( 本文出处: http://bbs.d-programming-language-china.org/thread-597-1.html )        图二、加载类型库        3、然后找到你要使用的组件文件 simple6.dll(tlb 文件也可以),选择接口后确认        图三、选择类型库中需要包装的接口        4、在适当的地方输入调用代码        QUOTE:                                #include "simple6.h"    // 包装类的头文件                void demo()                {                    // 已经进行过了 COM 初始化                    IDispSimple spDisp;        // 包装类的对象                    spDisp.CreateDispatch( _T("Simple6.DispSimple.1") )    //启动组件                    spDisp.xxx(...);    // 调用函数                    spDisp.ReleaseDispatch();    // 释放接口                }                使用 vc.net 的朋友,步骤如下:        1、建立一个 MFC 的应用程序        2、执行菜单“添加\添加类”,选择 MFC 分类中的“类型库中的MFC类”( 本文出处: http://bbs.d-programming-language-china.org/thread-597-1.html )        图四、添加类型库中的MFC类        3、选择组件文件 simple8.dll(或 tlb 文件),并选择需要包装的接口        图五、选择文件和接口        4、在适当的位置输入调用代码        QUOTE:                                #include "CDispSimple.h"    // 包装类的头文件                void demo()                {                    // 已经进行过了 COM 初始化                    CDispSimple spDisp;    // 包装类的对象                    spDisp.CreateDispatch( _T("Simple8.DispSimple.1") )    // 启动组件                    spDisp.xxx(...);    // 调用函数                    spDisp.ReleaseDispatch();    // 释放接口                }                示例四、使用 #import 方式调用组件        #import 方式在第七回中已经作过介绍,这里就不多罗嗦了。大家下载本回的示例程序后,自己去看吧。并且一定要掌握这个方法,因为它的运行效率是最快的呀。( 本文出处: http://bbs.d-programming-language-china.org/thread-597-1.html )小结        留作业啦。在我们以前所实现的所有组件程序中,只添加了接口方法(函数),而没有添加接口属性(变量),你自己练习一下吧,很简单的,然后写个程序调用看看。其实对于 VC 来说,调用属性和调用方法没有太大的区别(vc 把属性包装为 GetXXX()/PutXXX()或getXXX()/putXXX()的函数方式),但在另外一些语言中(比如脚本语言)则更方便,设置属性值是:对象.属性 = 变量或常量,获取属性值是:变量 = 对象.属性。        本回书至此做一了断,更多组件设计和使用的知识,且听下回分解......        --------------------------------------------------------------------------------        注1:多个自动化接口的实现方法,我们以后再说。        注2:将来介绍 ITypeLib::GetTypeInfo() 的时候,大家再回味 IDispatch::GetTypeInfo()吧。        注3:在后面介绍“事件”的时候,我们会自己真正去实现一个 IDispatch::Invoke() 函数。        注4:介绍多个双接口实现的时候,会谈到这个问题。( lastupdate:20070517 最新文章请访问http://www.d-programming-language-china.org )关于一大步成功社区:yidabu提倡在交流中学习,在分享中提高收集感兴趣的知识,写下心得,通过网络与别人一起分享理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台
页: [1]
查看完整版本: COM组件设计与应用11 IDispatch及双接口的调用