460 likes | 836 Views
第 10 章 分布式 COM(DCOM). DCOM 是 COM 的扩展,它可以支持不同计算机上组件对象与客户程序之间或者组件对象之间的相互通信,这些计算机可以在局域网内,也可以在广域网上,甚至通过 Internet 进行连接。对于客户程序而言,组件程序所处的位置是透明的,不必要编写任何处理远程调用的代码,因此, DCOM 也是 COM 的无缝扩展。. 10.1 DCOM 基本结构. 10.1.1 从 COM 转向 DCOM 本地进程外组件与客户程序运行在不同的进程空间,所以客户程序调用组件对象并不是直接进行的,而是用到了操作系统支持的一些跨进程通信方法。
E N D
第10章 分布式COM(DCOM) • DCOM是COM的扩展,它可以支持不同计算机上组件对象与客户程序之间或者组件对象之间的相互通信,这些计算机可以在局域网内,也可以在广域网上,甚至通过Internet进行连接。对于客户程序而言,组件程序所处的位置是透明的,不必要编写任何处理远程调用的代码,因此,DCOM也是COM的无缝扩展。
10.1 DCOM基本结构 • 10.1.1 从COM转向DCOM 本地进程外组件与客户程序运行在不同的进程空间,所以客户程序调用组件对象并不是直接进行的,而是用到了操作系统支持的一些跨进程通信方法。 但对于客户和组件运行在不同机器上的情形,DCOM只是简单只是简单地把本地跨进程通信用一个网络协议传输过程代替,客户程序和组件对象都感不到发生地过程,只是中间数据传递地路线更长一些。 当然,从COM到DCOM,并不仅意味着路线地更长,同时组件对象与客户程序协作运行地环境也发生了变化,从简单地单击环境转变到了网络环境。毕竟网络通信比单机系统环境下地跨进程通信要脆弱得多,所以为了保证协作过程地可靠性以及程序对异常事件地应变能力,客户和组件需要考虑更多地细节,而不是仅仅提供与应用有关地基本功能,比如,客户程序对调用返回错误地处理,调用被阻塞、被丢失地处理、以及异步调用地实现。
10.1.2 DCOM对象地定位 • 客户程序可以通过给定地机器名和对象CLSID来创建一个DCOM对象。可以调用COM库的基本创建函数(CoGetClassObject)创建远程组件对象。 • CoGetClassObject函数的第三个参数COSERVERUNFO结构包含了远程机器名和安全信息。在这个结构中,pwszName指定了远程机器的名字,pAuthInfo指定了创建对象的激活安全信息,如果为NULL,则使用系统缺省的安全设置。 • 可以简单地把pAuthInfo参数设置为NULL,并在第二个参数中指定远程对象地标志,从此就有两种方法提供远程对象地机器名信息,或者在创建函数地参数中指定COSERVERUNFO结构,或者使用DCOM配置工具指定异常机器名。
DCOM对象地定位 • COM库的创建函数得到了远程对象地位置信息后,再把对象创建地任务交给SCM,由SCM通过RPC与远程机器进行通信。SCM(程序名为Rpcss.exe)也是COM库的一部分,但它是一个单独的进程。SCM负责创建新的COM对象,也负责建立组件对象与客户程序之间的连接。如果要创建远程对象,它会通过RPC调用远程机器上的SCM,由远程机器上的SCM启动组件程序,并创建组件对象,然后返回到客户机器。 • 在此工程中,还要经过列集和散集的处理,包括创建代理对象和装载存根代码等,处理与本地一样。一旦组件对象创建完成之后,客户与组件之间的通信不再经过SCM,而是直接连接。
10.1.3 列集 • DCOM提供了一套复杂的列集和散集机制,它建立在RPC的基础上,由于RPC被定义为DCE(Distributed Computing Environment )标准的一部分,而DCE RPC定义了所有常用类型的数据表示方式,即网络数据表示法(NDR,network data representation)。为了使存根代理能够正确地对参数和返回结果进行列集和散集,它们应该使用一致地数据表示法(NDR),以便在不同地操作系统环境下也能够进行远程调用。
10.1.4 对象RPC • DCOM协议也被称为对象RPC(ORPC,object remote procedure call),它建立在DCE RPC协议地基础上,可用于各种基于组件的分布式系统。ORPC建立了一套面向对象的远程调用规范,指定了如何在网络上进行调用、对对象的引用如何表示和如何维护。 • 在Internet或Intranet网络环境下,ORPC仍使用标准的RPC数据包,附加上专用于DCOM的一些信息-接口指针标识符(IPID,interface point identifier)、版本信息和扩展信息-作为调用和返回的附加参数进行传递,其中IPID表示调用被处理的远程机器上特定对象的特定接口。DCOM客户程序必须周期性地“pinging”远程机器上地对象,以便保证客户与对象一直处于连接状态。 • 在开发DCOM组件时并不需要直接在ORPC层次上进行开发,因为组件程序和客户程序实际上只跟存根和代码对象进行通信。如果使用标准地列集法,则生成代理和存根完全和以前一样。
10.1.5 DCOM特性 • (1) 可伸缩性:随着用户数目的增加、数据量的不断增多,分布式应用系统的适应能力就反映了此系统的忧劣。使用DCOM建立起来的应用系统能很好地适应这种规模地变化。一方面,DCOM利用操作系统本身地可伸缩性-多CPU等,另一方面,DCOM提供了灵活地配置方案,运行不同的组件运行在不同地服务器上,DCOM地位置透明性保证了这种变化可以不必修改组件源程序。 • (2) 可配置性:使用DCOM模型建立地分布式软件系统可以很方便地对系统进行重新配置,包含服务器变化、客户程序的自动安装等特性。DCOMCNFG.EXE配置工具可使客户程序和组件程序在不改变代码的情况下适应不同的网络环境。
DCOM特性 • (3) 安全性:DCOM使用了Windows NT提供的可扩展安全性框架,在非NT平台上实现的DCOM也包括了一个与NT兼容的安全提供器。 • (4) 协议无关性:DCOM并不要求专门的网络协议,所以使用DCOM建立的分布式应用系统对网络有很强的适应能力,DCOM可以使用以下协议:TCP/IP、UDP、IPX/SPX以及NetBIOS。 • (5) 平台独立性:DCOM把平台相关的二进制标准和平台无关的标准隔离开来,并且,由于DCOM建立在DCE RPC的基础上,所以DCOM能很好地适应不同地系统平台。
10.2 对象激活 • 激活一个组件对象包括两种情形,或者是建立新的组件对象,或者是建立已有组件对象与客户程序地连接。 • DCOM使用两种方式提供远程服务器名。直接设置和在代码中显式指定远程服务器名。 • 10.2.1 创建DCOM组件(一) 此种方法只要在客户机上的组件配置信息中设置好相应地远程服务器名字,则客户程序不必考虑服务器的位置,它根本不用直接访问服务器名字信息。它只需要知道对象的CLSID,并调用COM库函数,COM库就会在组件配置信息中取到服务器名字,然后通过远程机器上的SCM创建组件对象。 在Windows系统平台中,远程服务器名字RemoteServerName 值被保存在系统注册表HKEY_CLASSES_ROOTS\APPID键下。其中的<DNS name>为服务器名字。在每个DCOM组件的CLSID入口项都有一个名字值指定了对应的AppID,从这一结构可以看出AppID可用于多个组件对象。
创建DCOM组件(一) • 客户机通过系统配置信息把组件对象的请求传到远程服务器上,但是,服务器不能再利用RemoteServer配置信息把创建请求传到另一台机器上。也就是X调用在Y上创建,机器Y的调用在Z上创建,那么X对象创建请求将在机器Y上得到响应,而不会被传到机器Z上。 • 可以用DCOMCNFG配置请求创建的远程机器名。如图10.5。
10.2.2 创建DCOM组件(二) • 用第一种方法并不是总能满足应用的需要,有些应用要求在程序运行工程中控制要连接的服务器,比如多人游戏程序、管理远程管理工具等。对于这样的应用,DCOM允许在创建函数中指定远程服务器名字。 (1) CoCreateInstanceEx创建一个组件对象,可以在参数中指定服务器信息。 (2) CoGetClassObject返回一个指向类厂对象的接口指针,客户用该指针来创建一个或多个实例对象,可以在参数中指定服务器信息。 (3) CoGetInstanceFromFile 创建一个新的实例,并根据文件进行初始化。 (4) CoGetInstanceFromISorage创建一个新的实例,并根据存储对象进行初始化。
创建DCOM组件(二) • COSERVERINFO结构记录了服务器名字信息和激活安全信息,一般使用缺省的激活安全信息,把pAuthInfo成员设为NULL。MULTI_QI结构可保存多个接口指针,客户程序可根据需要一次取到多个接口指针。 • 在程序中指定服务器名字不仅可用于诸如游戏或远程管理,也可以利用这种特性实现分布式应用系统的动态负载平衡功能。COM+已经实现了负载平衡功能。
10.2.3 远程创建进行会内组件:代理进程(surrogate) • 为了进程运行进程内组件,要求在远程机器上有代理进程(surrogate process)。除了可以远程启动进程内组件之外,代理进程还提供了下面的一些特性: (1) 进程内组件程序中的严重错误只影响到代理进程,不会使客户进程崩溃 (2) 一个代理进程可以同时为多个客户通过服务 (3) 客户可以保护自己避免靠不住的组件程序代码,只访问组件程序提供的服务 (4) 在代理进程中运行进程内服务可以使DLL享有代理进程的安全性
远程创建进行会内组件:代理进程(surrogate) • 如果一个进程内组件满足下列条件,则它将被装入代理进程: (1) 系统注册表中,在组件对象的CLSID关键字下必须要指定AppID值,以及对应的AppID关键字。 (2) 客户程序在创建对象实例时,必须设置CLSCTX_LOCAL_SERVER标志 (3) 组件对象的CLSID关键字下不指定LocalServer32、LocalServer、LocalService值。 (4) 组件对象的CLSID关键字包含InProServer32子键 (5) 在InProServer32子键中指定的DLL文件必须存在 (6) 组件对象对应的AppID键下指定DLLSurrogate值 如果组件对象的CLSID键下的LocalServer32、LocalServer或LocalService值指示了EXE的存在,则EXE程序将被优先执行,COM不再启动代理进程。
远程创建进行会内组件:代理进程(surrogate) • 为了激活代理进程,DLLSurrogate值必须被指定。如果要使用系统提供的代理进程,那么把DLLSurrogate的值设置为空串或者设置为NULL;如果要使用自定义代理进程,则把DLLSurrogate的值设置为代理进程的路径。为了激活。要在客户机中指定RemoteServerName,在服务机器上指定DLLSurrogate。 • 要一个组件程序的实例被装入,濒危所有的客户提供服务,而不管客户身份,则要在AppID注册表关键字下用RunAs值指定,如只为一个客户服务,就不要用RunAs值配置组件程序。
10.2.4 任何连接到指定的远程对象实例 • 在此可以利用名字对象连接到远程对象实例 客户程序调用MkParseDisplayName函数创建一个名字对象,然后调用名字对象的BindToObject函数执行绑定操作。如果要创建远程对象,那么必须利用COM对象的存储激活特性。 为了利用对象的存储激活特性,也就是说使COM对象在它保存数据的服务器上运行,必须在COM对象的AppID关键字下使用ActivateAtStorage值,在COM版本中,只有文件名字对象支持ActivateAtStorage特性。 在使用名字对象执行绑定操作时也可以通过程序代码来重新设置远程服务器名。通过绑定环境的参数指定BIND_OPTS2结构参数。在程序中可以先取出绑定环境对象中的BIND_OPTS2参数,然后设置新的COSERVERINFO结构参数,再设置到绑定环境对象中。
10.3 连接管理 • 10.3.1 更好的控制远程对象的生存期 不断的调用AddRef和Release会严重影响网络性能,DCOM优化了远程对象的AddRef和Release调用。其中使用了OXID(object exporter identifier,对象管理标识符),每个支持DCOM的机器上,都有一个被称为OXID解析器(OXID Resolver)的服务,OXID对象实现了IRemUnknown接口,DCOM通过RemAddRef和RemRelease方法管理远程引用计数。 • 10.3.2 pinging机制 为了检测客户程序是否非正常终止,DCOM提供了一种简单的方法“pinging”。在组件程序机器上,每一个被引出的对象(对应一个引出的OID)都有一个“pingPeriod”时间值和一个“numPingsToTineout”计数,它们组合起来决定整个事件。通常称为“ping周期”。DCOM包含了优化的“pinging”结构,ping信息由OXID解析器发送和接收,OXID解析器决定哪些OID在同一台机器上,并产生一个ping集。
10.4 并发管理 • 10.4.1 线程模型 1. 同样进程内的客户程序任何得到对象接口指针 2. 套间线程中客户调用对象的工程 3. 套间线程的客户跨进程调用的工程 4. 从另一个进程进入套间线程的过程。
10.4.2 消息过虑器 • 消息过虑器是一个简单的COM对象,它实现了IMessageFilter接口。
10.5 DCOM安全模型 • 10.5.1 安全性策略 (1) 访问安全性和激发安全性 (2) 对象的安全身份 (3) 保护数据 (4) 鉴定级别 (5) 模式级别 匿名(SecurityAnonymous) 身份鉴别(SecurityIdentification) 模仿(SecurityImpersonation) 委托(SecurityDelegation)
10.5.2 安全性配置 • 在此可以使用DCOMCNFG程序的“默认属性”和“默认安全机制”。 • DCOM也允许应用程序调用CoInitiallizeSecurity初始化DCOM安全层以便建立进程的鉴定和模仿局部。
第11章 自动化(Automation)对象 • 虽然自动化技术建立在COM的基础上,但自动化要比COM应用广泛得多。一方面,自动化继承了COM的很多优点;另一方面,自动化技术简化了COM的一些底层细节。 • 自动化的核心是IDispatch接口,每一个自动化对象都必须实现IDispatch。
11.1 自动化对象基础 • 11.1.1 自动化产生与发展 自动化技术的发展与VB和VBA有直接的关系。 首先,VBA以及发展成为大多数Microsoft应用程序扩展的标准,在这些应用程序中,宏语言跳过自动化对象访问应用程序中的数据。 其次,VB开发工具的成功应用也推动了自动化对象的发展。
11.1.2 属性和方法 • 方法和属性是自动化对象的两个基本特性,方法是自动化对象所提供的功能服务;而属性是指自动化对象的数据特征。属性是一个值,它既可以被设置,也可以被获取。 • 一般来说,获取属性值不需要参数,但有一些属性可以具有索引值。属性的索引值有点像参数,它不一定非要整数,并且在索引中也可以有多个元素。 • 方法可以具有零个参数和多个参数,它们既可以设置也可以获取对象数据,最常见的是完成某些动作。 • 自动化的属性和方法都有符号化的名字,客户程序通过名字就可以访问到自动化对象的属性个方法。这比通过vtable才能访问到COM对象的成员函数要方便。
11.3.1 类型库和ODL • 组件对象的类型信息是指它与外界进行交互的一些必要信息,包括组件的CLSID、它所支持接口的IID、接口的每个成员函数、成员函数的参数和返回值等等,类型信息中的数据类型也可以是自定义的数据类型。 • 类型信息是客户程序和组件之间进行通过的基础,不管通过什么样的方式,客户程序在访问组件对象之前必须得到基本的类型信息。 • Microsoft扩充了IDL形成ODL(object description language,对象描述语言),可以描述组件对象的类型信息。一般来说,一个组件对象的类型信息包括每个接口的类型信息和对象的类型信息,接口类型信息的描述方法与IDL完全兼容。接口类型信息使用interface或dispinterface关键字描述,对象类型信息使用coclass关键字进行描述。经过MIDL或者MkTyLib工具可编译得到类型库。通常一个组件程序中的对象放在一个ODL文件中,并用library关键字描述库信息,包括类型库的ID(即LIBID)、类型库所使用的语言、版本等。在ODL文件中也可以使用importlib引入其它类型库信息。
类型库和ODL • 用MIDL实用工具编译ODL文件可以得到类型库文件,其后缀为TLB,也可以产生相应的.h头文件。VB或者VBA可以使用类型库来浏览组件对象的方法和属性,它也可以利用类型库增强对属性和方法的访问。 • 在实现自动化对象的IDispatch接口时,可以利用类型库向客户程序提供类型信息,这可以避免繁琐的类型处理。
11.1.4 IDispatch接口 • 在接口IDispatch就中,前两个成员函数GetTypeInfo和GetTypeInfoCount用于处理对象的类型信息。在GetTypeInfoCount成员函数中,如果对象提供了类型信息,则输出参数pctinfo的返回值为1;否则为0。GetTypeInfo成员函数返回对象的类型信息。 • 成员函数GetIDsOfNames用于返回方法或属性成员及其参数名字的DISPID, IDispatch用分发ID管理属性和方法。 • 自动化接口的分发ID是一个整数,0或者负数有特殊含义。如表11.1 • 成员函数Invoke是一个关键函数,客户程序必须通过Invoke函数才能访问属性和方法,也可以说此函数是自动化对象的命令翻译器。此函数的第一个参数dispIdMember给出了分发ID,它将根据此分发ID执行有关的属性访问函数或者方法函数。
11.1.5 自动化兼容的数据类型 • 在自动化对象的方法和属性中,必须使用自动化兼容的数据类型。自动化对象使用的基本数据类型为VARIANT结构类型,在Invoke函数的参数中,pDispParams参数中表示方法或属性参数的类型为VARIANTARG结构,pVarResult参数的类型为VARIANT结构,其实两个的定义完全相同。 • VARIANT结构中,大多数类型已经很熟悉,但有几个类型要做简单介绍 (1) 布尔型VARIANT_BOOL。实际上就是短整型,1为真,0为假 (2) 货币类型CY (3) 日期类型DATE。日期类型实际上是一个浮点数,整数部分表示自1899年12月30日以来的天数,小数部分为时间值。
自动化兼容的数据类型 (4) Basic字符串类型BSTR。BSTR是一个双字节表示的字符串类型,以0结尾,但特殊的是,在字符串开始的前4个字节指定了字符串的长度。如图11.1。 由于BSTR是使用了长度计数器,所以BSTR作为字符串类型,在进行各种处理时效率要比普通的做的长高的多。OLE提供了一组API函数用来处理BSTR,包括SysAllocString、SysAllocStringLen、SysFreeString、SysReAllocString、SysReAllocStringLen以及SysStringLen等。 (5) SAFEARRAY类型。此类型实际上是一个结构类型,但它用于描述各种数组,由于SAFEARRAY指定了数组每一维的边界信息,所以可以认为它是安全的。 OLE也提供了一套API函数用来处理SAFEARRAY结构,比如SafeArrayAllocData、 SafeArrayDestoryData等将近20个函数。
自动化兼容的数据类型 • 假如Invoke函数希望在成员调用中获得一个BSTR字符串参数,但它得到的是long或者double,那么Invoke函数在调用内部处理函数之前,必须先把它转化为BSTR。甚至得到的是一个Idispatch指针,也得把其转换为BSTR。 • OLE提供了两个函数可以完成类型转换功能:VariantChangeType和VariantChangeTypeEx,利用这两个函数可以把一个VARIANT变量转换为另一个VARIANT变量。还有Variant <type> From <type>,比如VarR4FromI2, VarUI2FromDisp等。 • Invoke函数还可以使用另一个API函数DispGetParam来处理类型转换。
11.1.6 参数顺序、可选参数和命名参数 • Invoke函数的参数pDispParam。 pDispParam参数的数据类型为DISPPARAMS。关于此结构的三个问题如下: 1. 参数顺序:在rgarg数组中,参数的顺序与客户程序中调用的参数左右顺序刚好相反。 2. 可选参数:在ODL文件中,可以把方法的参数记为可选的(optional),但是这样的参数在DISPPARAMS结构中也会出现。为了判断一个参数是否为可选参数,Invoke函数检查参数的vt域是否为VT_ERROR,如果vt域为VT_ERROR并且scode域为DISP_E_ PARAMNOTFOUND,则此参数为可选参数。
参数顺序、可选参数和命名参数 3. 命名参数:不管是可选参数还是按反序指定的参数,统称为定位参数(positional argument),与之相对应的另一种参数为命名参数(named argument)。 DISPPARAMS结构的cNamedArgs成员指定了在rgarg数组中有多少个命名参数。如果cNamedArgs为0,则表示没有命名参数。 命名参数是指以名字指定的参数,命名参数可以不受次序约束。不过命名参数和定位参数不能相互交叉,而且rgvarg数组的前cNamedArgs个参数为命名参数,后面的参数为定位参数;对应于客户程序调用的参数顺序,必须先指定定位参数,再紧跟着命名参数。
11.1.7 IDispatchEx接口 • 使用IDispatch接口对于一般的应用来说已经足够了,但由于它所描述的类型信息是静态的,在运行工程中类型信息不会被改变,所以在编译时刻通过类型库就可以获得所有的类型信息。然而,在一些脚本语言中,需要造运行时刻动态提供类型信息,比如VBScript、JavaScript或DHTML对象模型等要求一个更加灵活的接口。IDispatchEx正好这种要求。
11.2 自动化对象实现 • 11.2.1 类型库支持 接口Idispatch成员函数GetTypeInfoCount和GetTypeInfo的主要目的是提供类型库支持,控制器可以在运行时刻调用GetTypeInfoCount函数判断对象是否提供接口类型信息,如果对象支持类型信息,则控制器程序可以调用GetTypeInfo函数获取类型信息。如果对象没有提供只需把输出参数pctinfo的返回值置为0。 提供类型库信息的步骤如下: 1. 利用ODL语言编写一个ODL文件, 2. 然后利用MIDL实用工具生成TLB文件。使用下面的命令可以产生类型库文件(以及相应的C/C++头文件): MIDL /h <header>.H/tlb <library>.TLB <file>.ODL
自动化对象实现 • 有了TLB文件之后,就可以实现IDispatch接口的Get-TypeInfoCount和GetTypeInfo函数 GetTypeInfoCount函数非常简单,只需要把输出函数pctinfo设置为1既可。 GetTypeInfo函数则需要调用OLE提供的API函数把TLB文件装入到内存中,然后向控制器返回ITypeInfo接口指针。
11.2.2 Invoke函数实现 • 根据实际情况,可以采用两种方法来实现GetIDsOf-Names函数。 1. 由于自动化对象知道自己所有的方法和属性以及它们的分发ID,因此可以利用这些知识提供GetIDsOf-Names服务。可通过查表或者switch语句实现从名字到分发ID的映射。如果对象的属性和数目较少,那么可以直接用switch或者if语句实现。如果较多,那么可以把名字和分发ID信息放在一张表中, GetIDsOfNames函数直接查表既可。
Invoke函数实现 • 2. 利用自动化接口的类型信息对象实现GetIDsOfNames函数。因为类型库本身已经包含了自动化对象接口的成员与分发ID之间的映射关系,所以ITypeInfo接口提供这样的服务。 OLE的API函数DispGetIDsOfNames简单地封装了ITypeInfo接口的GetIDsOfNames成员函数用于提供这样的服务, 第一种方法适合于比较简单的情形,Invoke函数负责所有的成员处理,包括参数提取等待,然后根据成员分发ID的不同盟执行不同的分支。 第二种方法也要用到类型库信息对象ITypeInfo接口指针。在执行MIDL产生一个用C/C++语言描述的.h头文件,头文件不仅包含自动化接口的定义,也包含一个只与自动化成员有关的自定义接口。
11.2.4 多语种-本地化 • 在Idispatch接口的多个函数中的LCID(locale identifier,本地化标识,有时也写为LocaleIE)参数,它指示了控制器所使用的语言环境。LCID被定义为一个32位的整数,通常它对一个组件对象有以下一些影响: (1) 字符串资源的表达、排序、比较等操作 (2) 日期、时间的表达以及其它一些操作 (3) 货币的格式 自动化对象的每一个方法和属性在不同的语言环境中可以使用不同的名字,比如MoveTo--移动到。 实现语言支持的合理做法是支持两种语言,一种是对象应用的主要语言环境,另一种是可以适用于任何语言环境的语言-英语。
11.2.5 用CreateStdDispatch函数实现自动化对象 • OLE提供了一个标准的IDispatch接口实现,可以通过API函数CreateStdDispatch创建一个标准分发对象(standard dispatch object),标准分发对象只实现一个IDispatch接口。 • CreateStdDispatch函数要求提供Iunknown接口指针以及一个指向对象自身的pvThis指针,更为重要的是一个指向类型信息对象的ITypeInfo接口指针ptinfo。 • 使用CreateStdDispatch函数实现自动化对象的用法比较简单,首先为自动化对象类增加一个初始化函数,以便类厂的创建函数调用。 • 因为自动化对象已经聚合了标准分发对象,所以它不必要实现IDispatch接口,而只需在QueryInterface函数中把标准分发对象的IDispatch接口暴露出去既可。
11.3 自动化对象应用 • 11.3.1 双接口 自动化控制器通过IDispatch接口可以调用自动化对象的所有方法和属性,但由于这样调用是通过Invoke函数是间接进行的,并且在Invoke函数中要进行参数类型检查或者其它一些类型信息处理,因此效率要低一些。客户程序希望能直接通过vtable调用到成员函数代码。为了满足两种情形,自动化对象可以在实现IDispacth接口的基础上,把方法和属性函数也以接口成员函数的方法暴露出去,这样就形成了双接口(dual interface)。
11.3.2 迟绑定和早绑定 • 如果自动化控制器利用对象的类型库,在编程时刻对参数类型和返回值进行检查,并直接用分发ID调用Invoke函数,这种调用方法就称为早绑定(early binding)技术。 • 相应地,控制器也可以在运行时刻通过IDispatch接口的成员函数获得类型信息,当它访问一个属性或方法时,它调用IDispatch::GetIDsOfNames,以便根据符号化名字找到分发ID,这种称为迟绑定技术。 • 选择迟绑定或早绑定取决于控制器的运行环境,而调用效率并不成为判断的依据。正真限制速度的因素是IDsipatch::Invoke调用。
11.3.3 自动化集合对象 • 集合对象也是一个自动化对象,但它有一些特殊的要求。 (1) 首先,作为一组同类对象(或数值)的容器对象,它必须通过枚举这些成员的方法。 (2) 它必须支持Add、Remove和Item方法以及Count属性。 客户程序通过Add方法增加成员;通过Remove方法删除成员;通过Item方法可以访问集合中的任意成员。
11.3.4 以IDispatch作为出接口 • 当IDispatch接口作为对象的出接口时,对象通过调用Invoke函数来激发事件。这时,位于客户程序或控制器一方的事件接收器实现IDispatch接口,它的Invoke函数负责处理各种事件;而自动化对象则变成了出接口的客户,它在调用Invoke函数时,必须设置正确的参数。然而,由于出接口是由对象调用的,所以它知道每个事件所对应的分发ID以及参数和返回值的类型,因此,事件接收器除了实现Invoke函数和IUnknown的三个函数之外,他不必再提供类型信息支持。从这个角度来讲,由IDispatch作为出接口要简单一些。
11.3.5 自动化控制器 • 如果要自己编写一个自动化对象控制器以便允许用户编写脚本代码控制自动化对象,那么以下一些原则可供参考: (1) 对象创建机制--COM命名机制 (2) 对象析构机制--调用Release或者CoFreeUnUsed-Libraries,或者set obj=nothing(VB) (3) 如何连接的到一个已经在运行的自动化对象。通过名字对象或者GetObject可以根据路径名或者ProgID连接 (4) 如何访问自动化对象的属性。obj.property。也可以使用obj::property、 obj[property]等 (5) 调用自动化方法。与属性非常类似,甚至可采取同样的语法规则。 (6) 提供事件处理机制。 (7) 对于实现多个自动化接口的对象,控制器应提供QueryInterface函数的功能,允许用户的脚本代码从一个接口转向另一个接口。 (8)向用户提供UI形式的对象类型信息。