《自动化对象学习.pptx》由会员分享,可在线阅读,更多相关《自动化对象学习.pptx(25页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、1一个类型库可以包含多个COM对象,这些COM对象可以实现多个接口,而且一般而言实现了IDispatch接口(不是必须).为了标识这些类型库,也使用GUID来作为它的唯一标识LIBID.并且也在注册表中注册,注册位置是HKEY-CLASSES_ROOTTypeLib,注册内容主要指明类型库所描述的对象的载体(dll文件等)的位置.VB,Java等语言的开发者不需要直接面对类型库.相反,它是由编译器环境(VB虚拟机,Java虚拟机)来解释它.这样它使得开发者在开发期能够浏览接口的相关信息.(以VB为例,通过Reference添加对类型库的引用后,使用Object Browser就可以查看COM接
2、口了,另一个工具OLE/COM Object Viewer使用更加方便).而开发人员只需要使用宿主语言简单的语法,非常方便地使用COM.(烦心事交给编译器的开发者去吧!我们看到,如果不是使用COM,而是以一般的库函数的形式,在VB这样的高端应用中使用起来就没有这么简便(对最终开发者而言).每一样复杂的技术,在使用者的舒适的背后,是底层开发者的艰辛)当然,如果愿意,C+编译器也可以利用类型库.Visual CIDE中的ClassWizard和CBuilderIDE,DElphi中的importType Library命令都可以读入组件的类型库,并利用其中的信息产生C代码。客户程序利用这些代码可以
3、使用COM组件。第1页/共25页2并不是只有IDE的开发者才知道怎样解析类型库.为了操作类型库,Windows提供了一些API(LoadTypeLib 和LoadRegTypeLib等)和COM接口(ITypeLib和ITypeInfo等).1.LoadTypeLib可以根据指定的文件名装载类型库,并返回ITypeLib接口.2.使用LoadRegTypeLib可以根据类型库的LIBID查找注册表,找到类型库文件,返回ITypeLib接口.3.ITypeLib接口代表了类型库本身.使用其GetTypeInfoofGuid根据接口的IID或者使用GetTypeInfo根据接口在类型库中的索引号可
4、以返回ITypeInfo接口.4.ITypeInfo接口则代表了接口的全部信息.包括有哪些方法,方法的签名等等.如果接口是IDispatch接口,则还可以使用GetIDsofNames函数来根据方法的名字得到其分发ID,并使用Invoke函数通过方法的分发ID来执行这个方法.因此,为了在编译时刻了解接口的信息,客户程序要么得到COM组件的IDL文件(使用头类型定义头文件,在代码中通知编译器接口的类型,如C+),要么得到它的类型库文件(代码中没有准确的信息,由IDE环境从类型库中读取接口类型信息,如VB),才能顺利地构造客户应用程序,从而使用COM对象.第2页/共25页3无论是通过头文件,还是通
5、过类型库,我们在开发客户程序时都有关于接口的先验知识.这些先验信息帮助我们顺利地编译客户程序.这种方式我们有时称为静态调用,或者早绑定(early binding).但是,还存在这样的情况,有的语言在开发过程中并没有经过编译阶段,而是直接以源代码的形式被配置发布.在运行时才被解释运行.比如以HTML为基础的脚本语言.(VBScript,JavaScript等).它们在浏览器或Web服务器的环境中执行.脚本代码以纯文本的形式嵌入在HTML文件中.为了丰富脚本的功能,它们也可以创建COM对象,执行特殊的功能,比如访问数据库等等.比如:var obj=new ActiveXObject(“LuBen
6、jie.AutoObj);alert(obj.Hello();在脚本引擎中,目前还不能使用类型库或其他的先验知识来描述接口的信息.这意味着对象自身要帮助脚本解释器,将文本形式的脚本代码翻译为有意义的方法调用.这种方式我们称为动态调用,或者晚绑定(late binding).为了支持晚绑定,COM定义了一个接口,用来表达这种翻译机制,这个接口就是IDispatch.分发接口有时称为自动化接口,实现了此接口的对象称为自动化对象.自动化接口的定义如下:2 IDispatch接口第3页/共25页4class IDispatch:public IUnkownpublic:HRESULT GetTypeI
7、nfoCount(unsigned int FAR*pctinfo);/如果对象提供类型支持,则返回1,否则0.客户在获取类型信息之前先使用此函数进行判断.HRESULT GetTypeInfo(unsigned int iTInfo,LCID lcid,ITypeInfo FAR*FAR*ppTInfo);/一般给iTInfo赋值0,返回指向对象类型信息的ITypeInfo接口指针,通过ITypeInfo接口可以访问该自动化接口的所有类型信息.HRESULT GetIDsOfNames(REFIID riid,OLECHAR FAR*FAR*rgszNames,unsigned int cN
8、ames,LCID lcid,DISPID FAR*rgDispId);/返回指定名字的方法或属性的分发ID.IDispatch使用分发ID管理接口的属性和方法.rgszNames 指定属性或方法的名字,rgDispId返回其分发IDHRESULT Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS FAR*pDispParams,VARIANT FAR*pVarResult,EXCEPINFO FAR*pExcepInfo,unsigned int FAR*puArgErr);/是命令的翻译器。客户程
9、序通过invoke函数访问方法或属性。客户给定分发ID dispIdMember、输入参数pDispParams。invoke返回输出参数pDispParams.自动化对象所有的方法和属性的调用都通过invoke函数来实现。使得运行时刻动态绑定属性和方法并进行参数类型检查成为可能.第4页/共25页5当一个脚本引擎首次尝试访问一个对象时,它使用QueryInterface向对象请求IDispatch接口.如果请求失败,则不能使用此对象.如果成功,则继续调用GetIDsofName方法,得到方法或属性的分发ID号.通过此ID号,调用Invoke方法,就可以调用想要调用的方法.分发接口与普通接口的区
10、别在于,接口的逻辑功能是如何被调用的.普通的COM接口是以该方法的静态的先验知识为基础.而分发接口是以该方法的预期的文字表示为基础.如果调用者正确地猜测出方法的原型,那么此调用可以被顺利地分发,否则不能.假设有一个自动化对象CMath,它只实现了分发接口,进行加减乘除的工作.这些具体的工作由内部函数来完成.并没有向外界提供接口.这些计算功能由Invoke函数根据分发ID来调用特定的函数.uuid(C2895C1F-020E-4C1F-8A65-F59094DFBD97)dispinterface DMath /dispinterface 关键字说明这是一个分发接口.properties:met
11、hods:id(0)long Add(long Op1,long Op2);/0,1,2,3分别是分发ID id(1)long Substract(long Op1,long Op2);id(2)long Multiply(long Op1,long Op2);id(3)long Divide(long Op1,long Op2);此对象的虚表及其分发表示意图如下:第5页/共25页6自动化对象的虚表和分发表.pVtableQueryInterfaceAddRefReleasem_pDataGetTypeInfoCountGetTypeInfoGetIDsofNamesInvoke接口指针Add
12、SubstractMultiplyDivide0123分发表组件的实际业务功能IUnknownIDispatch第6页/共25页7自动化对象可以只实现分发接口:class CMath:public IDispatchpublic:/来自IUnknown的三个函数virtual HRESULT _stdcall QueryInterface();virtual ULONG_stdcall AddRef();virtual ULONG_stdcall Release();/来自IDispatch的四个函数 HRESULT GetTypeInfoCount();HRESULT GetTypeInfo
13、();HRESULT GetIDsOfNames();HRESULT Invoke();/此COM对象只能通过分发接口给外界提供服务.虽然这样做显得别扭,有舍近求远之嫌,但是,原理上是可行的.第7页/共25页8更常用地,我们把具体的计算功能也作为接口直接暴露出去,我们从IDispatch派生一个接口IMath.object,uuid(2756E11C-A606-482F-969C-14153E1D1609),dual/说明是一个双接口interface IMath:IDispatch properties:methods:id(0)HRESULT Add /0,1,2,3分别是分发ID (in
14、 long Op1,in long Op2,out,retval long*pResult);id(1)HRESULT Substract (in long Op1,in long Op2,out,retval long*pResult);id(2)HRESULT Multiply (in long Op1,in long Op2,out,retval long*pResult);id(3)HRESULT Divide (in long Op1,in long Op2,out,retval long*pResult);自动化对象实现双接口:第8页/共25页9class CMath:public
15、 IMathpublic:/来自IUnknown的三个函数virtual HRESULT _stdcall QueryInterface();virtual ULONG_stdcall AddRef();virtual ULONG_stdcall Release();/来自IDispatch的四个函数 HRESULT GetTypeInfoCount();HRESULT GetTypeInfo();HRESULT GetIDsOfNames();HRESULT Invoke();/来自IMath的三个函数HRESULT Add(long Op1,long Op2,long*pResult);H
16、RESULT Substract(long Op1,long Op2,long*pResult);HRESULT Multiply(long Op1,long Op2,long*pResult);HRESULT Divide(long Op1,long Op2,long*pResult);/此COM对象同时通过分发接口给外界提供分发调用服务;通过IMath接口直接通过虚表来提供普通的服务.实现双接口的自动化对象的虚表和分发表:第9页/共25页10实现双接口的自动化对象的虚表和分发表pVtableQueryInterfaceAddRefReleasem_pDataGetTypeInfoCount
17、GetTypeInfoGetIDsofNamesInvoke接口指针AddSubstractMultiplyDivide0123分发表IUnknownIDispatchIMath第10页/共25页113 自动化接口的实现分发接口的四个函数从功能上来说分为两组:1.GetTypeInfoCount与GetTypeInfo函数表示对类型库的支持.通常客户并不需要从分发接口的这两个函数中来访问类型库.如果愿意,客户可以借助IDE生成封装类,或者直接使用操作类型库也可以.但如果真要实现它,那么:1.提供类型库文件(MIDL编译器对IDL编译的结果)2.GetTypeInfoCount返回1,否则返回0
18、;3.GetTypeInfo 使用LoadTypeLib得到ITypeLib接口.然后得到 ITypeInfo接口.一旦客户得到ITypeInfo接口指针就可以完全地了解接口的类型及其所支持的属性和方法。2.GetIDsOfNames和Invoke完成函数的分发调用.GetIDsOfNames有两种实现方法:1.由自动化对象自己实现。它当然知道自己所有的方法和属性的分发ID。使用switch case或者如果数目太多的话,使用表格进行查表.第11页/共25页12HRESULT GetIDsOfNames(REFIID riid,OLECHAR FAR*FAR*rgszNames,unsigne
19、d int cNames,LCID lcid,DISPID FAR*rgDispId)/假设cNames=1,即一回只查一个名字.char*str=OLE2T(rgszzNames0);if(strcmp(“Add”,str,3)=0)rgDispId0=0;/加法返回0 else if(strcmp(“Substract”,str,8)=0)rgDispId0=1;/减法返回1 else if 2.如果实现了GetTypeInfo,那么直接从其中得到ITypeInfo指针,然后使用这个指针的GetIDsOfNames方法即可.(绕了一大圈,但是也可行).HRESULT GetIDsOfNam
20、es()ITypeInfo*pITI;GetTypeInfo(&pITI);pITI-GetIDsofNames();pITI-Release();第12页/共25页13Invoke函数的实现。1。可以根据分发ID,逐个分支处理,可以使用内部函数,或者,如果是双接口,分支内部直接使用IMath接口的功能函数.HRESULT Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS FAR*pDispParams,VARIANT FAR*pVarResult,EXCEPINFO FAR*pExcepInfo,u
21、nsigned int FAR*puArgErr);switch(dispIdMember)case 0:/作加法,直接实现,或者调用内部函数.case1:/减法 2。使用类型信息指针。如果实现了GetTypeInfo,那么直接从其中得到ITypeInfo指针,然后使用这个指针的Invoke方法即可.(也绕了一大圈,但是也可行).HRESULT Invoke()ITypeInfo*pITI;GetTypeInfo(&pITI);pITI-Invoke();pITI-Release();第13页/共25页144.使用自动化对象.对于自动化对象的使用,根据其实现接口和对类型库的支持程度不同,有不同
22、的使用方法:1.只实现了分发接口,没有提供类型库.只能使用晚绑定.2.实现了分发接口,提供了类型库,当然可以使用晚绑定,也可以使用DISPID绑定(早绑定的一种,为了区分起见就命名为DISPID绑定).3.实现了双接口,提供了类型库,那么可以使用晚绑定,DISPID绑定和早绑定.晚绑定-DISPID绑定-早绑定 性能越来越高.灵活性越来越低.第14页/共25页154.1 晚绑定晚绑定.一般的COM对象都只能使用早绑定.但是自动化对象可以使用晚绑定.是重要特色之一.开发阶段不进行类型检查,运行时决定组件的功能.代价昂贵,速度最慢.灵活性最高.服务器接口发生变化(比如说分发ID变了),客户程序不用
23、重现编译.1.使用VB Dim obj as Object Set obj=CreateObject(“MathLib.Math”)/动态创建.obj.Add(10,20)/结果为30 Set obj=Nothing/释放对象 注意,在编译时刻并没有进行类型检查,obj.Add(10,20)纯属猜测!如果方法的名字不符合或者参数不符合,都将引起运行时错误.2.使用C+.使用C+我们能更清楚地看到分发调用的过程.(虽然晚绑定一般是针对VB这样的语言的)第15页/共25页16首先看函数调用调用的参数类型typedef struct tagDISPPARAMS VARIANTARG*rgvarg;/
24、参数数组,类型为VARIANT,大小为cArgsDISPID*rgdispidNamedArgs;/命名参数的ID数组.UINT cArgs;/参数的个数UINT cNamedArgs;/命名参数的个数 见MSDN文档 DISPPARAMS;其中VARIANT是一个结构体,结构体中包含巨大的Union和一个指示实际类型的域vt.见MSDN文档.在使用晚绑定时,只能使用VARIANT所支持的数据类型.客户的调用代码:IDispatch*pD;HRESULT hr=CoCreateInstance(CLSID_Math,NULL,CLSCTX_SERVER,IID_IDispatch,&pD)/创
25、建自动化对象,返回自动化接口LPOLESTR lpOleStr=L”Add”;/加法,注意只是一个字符串DISPATCH dispid;/加法字符串对应的分发ID存在此,下面先找到它第16页/共25页17pD-GetIDsofNames(IID_NULL,lpOleStr,1,LOCAL_SYSTEM_DEFAULT,&dispid);/得到加法的分发IDDISPPARAMS dms;/准备作加法的参数memset(&dms,0,sizeof(DISPPARAMS);dms.cArgs=2;/有两个参数VARIANTTAG*pArg=new VARIANTTAGdms.cArgs;/动态分配内
26、存dms.rgvarg=pArg;memset(pArg,0,sizeof(VARIANT)*dms.cArgs);dms.rgvarg0.vt=VT_I4;/第一个参数是长整数dms.rgvarg0.lVal=10;/值为10dms.rgvarg1.vt=VT_I4;/第二个参数也是长整数dms.rgvarg1.lVal=20;/值为20VARIANTARG vaResult;/输出结果的参数VariantInit(&vaResult);hr=pD-Invoke(dispid,IID_NULL,LOCAL_SYSTEM_DEFAULT,DISPATCH_METHOD,&dispparams,
27、&vaResult,0,NULL);/使用invoke,根据分发ID进行计算.输入计算参数,提供返回参数pD-Release();/释放接口第17页/共25页18注意以上计算过程,我们只是使用了分发接口,我们猜测了加法的名字和参数.我们事先没有使用到自动化对象的任何信息.不需要包含接口声明的头文件.编译时刻没有进行任何类型检查.如果猜测失误将引起运行时错误.第18页/共25页194.2 DISPID绑定如果提供类型库,那么就可以在编译时进行类型检查.VB中使用Reference导入类型库.我们就可以象VB中固有的数据类型一样使用COM对象.编译器将根据组件中的类型信息检查代码中的语法和参数类型
28、.VB为方法和属性缓存一个DISPID.避免在运行时刻去查询方法或属性的分发ID.以上措施,可以避免出错,提高性能.组件的接口改变时,要重新编译客户程序.Dim obj as New MathLib.Math obj.Add(10,20)/返回30 不是猜测的!如果不符合,则编译会出错!这是类型库起的作用.下面看C+中如何使用DISPID绑定MFC提供了COleDispatchDriver类,可以用来使用DISPID绑定来访问自动化对象:第19页/共25页20COleDispatchDriver类是MFC提供的封装类,它通过自动化对象的类型库把原自动化对象的方法和属性的分发ID硬性地记录下来,
29、把原来的方法和属性在封装类中进行封装.使得用户避免复杂的invoke参数序列,COleDispatchDriver 有一个数据成员m_lpDispatch,它包含了对应组件的IDispatch接口指针。COleDispatchDriver提供了几个成员函数包括InvokeHelper GetProperty SetProperty,这三个函数通过m_lpDispatch调用invoke函数。COleDispatchDriver的其他成员管理IDispatch接口指针,CreateDispatch根据CLSID创建自动化对象,并把IDispatch接口指针赋给m_lpDispatch成员。Att
30、achDispatch使得当前的COleDispatchDriver与某个自动化对象联系起来。DetachDispatch则取消这种联系。第20页/共25页21两种使用方式:1.根据组件的类型库生成COleDispatchDriver的派生类。从ClassWizard对话框的Add Class中选取From a type library,指定类型库文件,IDE为我们生成COleDispatchDriver的派生类的派生类。针对原自动化对象的属性和方法分别生成此派生类的函数。这些函数在实现时调用COleDispatchDriver的SetProperty,GetProperty和InvokerH
31、elper函数。2.如果我们已经得到了自动化对象的IDispatch指针,(如果没有,当然可以调用CreateDispatch等方法.)使用AttachDispatch把自动化对象与COleDispatchDriver对象联系起来通过SetProperty、GetProperty访问对象的属性,通过InvokerHelper访问对象的方法。第21页/共25页22以第一种方法为例,使用IDE的添加类向导from type library.选择类型库,则产生以下类:class IOMath:public COleDispatchDriverpublic:long Add(long Op1,long
32、 Op2);long Substract(long Op1,long Op2);long Multiply(long Op1,long Op2);long Divide(long Op1,long Op2);long IOMath:Add(long Op1,long Op2)static BYTE params=VTS_I4 VTS_I4;long result;InvokeHelper(0 x1,DISPATCH_METHOD,VT_I4,&result,params,lOp1,lOp2);第22页/共25页234.3早绑定如果实现了双接口,又有类型库的支持.那么就可以使用早绑定.实际上这就是一般的COM对象的使用方式.即直接使用虚表来调用接口的方法.而没有使用GetIDsofName和Invoke函数.在VB中使用Reference引进类型库后.代码与前一种方法一样.Dim obj as New MathLib.Math obj.Add(10,20)而C+语言则是按照普通的COM接口一样,不用理会分发接口即可.第23页/共25页245自动化对象编程实践1.MFC的支持2.ATL的支持 见 第五章,第十一章.以及其他文档第24页/共25页25感谢您的观看!第25页/共25页