c#2005net30高级编程(第5版)第23章COM的互操作性.doc
《c#2005net30高级编程(第5版)第23章COM的互操作性.doc》由会员分享,可在线阅读,更多相关《c#2005net30高级编程(第5版)第23章COM的互操作性.doc(39页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第23章 COM的互操作性230第 章 COM的互操作性如果您在学习.NET之前编写过Windows程序,通常没有时间和资源用.NET再重新编写以前的程序。有时重写代码有助于做一些修订,重新思考应用程序的体系架构,从长远来看,还有助于提高效率,更便于用新技术添加新特性。但是,我们不会为使用一种新技术而重写已有的代码。我们本来有数千行可运行的代码,重写它们需要的精力太多,还不如把它们迁移到托管的环境中。这也同样适用于Microsoft。在命名空间System.DirectoryService中,Microsoft并没有重新编写COM对象来访问有层次的数据存储,这个命名空间中的类实际上是访问ADS
2、I COM对象的包装器。System.Data.OleDb命名空间也是这样,由这个命名空间中的类所使用的OLE DB提供程序包含相当复杂的COM接口。我们自己的解决方案也会面临相同的问题。如果在.NET应用程序中要使用已有的COM对象,或者要编写在旧COM客户程序中使用的.NET组件,就应使用本章介绍的COM互操作性。如果没有要与应用程序集成的COM组件,或旧COM客户程序要使用一些.NET组件,就应跳过本章。本章主要内容如下: COM和.NET技术 在.NET应用程序中使用COM对象 在COM客户程序中使用.NET组件 调用本地方法的Platform Invoke(平台调用)与其他章节一样,
3、本章的示例代码也可以从Wrox网站上下载。23.1 .NET和COMCOM是.NET以前的技术。COM定义了一个组件模型,在该模型中,组件可以用不同的编程语言编写。用C+编写的组件可以在VB客户程序中使用。组件还可以在本地的进程中使用,跨进程使用或在网络上使用。看起来是不是很熟悉?当然,.NET的目标也是这样。但这些目标的实现方式是不同的。COM概念使用起来越来越复杂,已经不能扩展了。.NET达到了与COM类似的目标,但引入了新概念,实现起来更容易。即使到了今天,使用COM和.NET交互操作的主要问题是要理解COM。是COM客户程序使用.NET组件,还是.NET应用程序使用COM组件并不重要,
4、而是必须理解COM。所以这里首先比较COM和.NET。如果您已经熟练掌握了COM技术,本节将是COM知识的复习。否则,您将学习到COM的概念 现在是使用.NET 我们不再需要在日常事务中处理它了。但是,在把COM技术集成到.NET应用程序中时,COM的问题仍旧存在。COM和.NET有许多类似的概念和不同的解决方案。下面将讨论: 元数据 释放内存 接口 方法绑定 数据类型 注册 线程 错误处理 事件处理23.1.1 元数据在COM中,组件的所有信息都存储在类型库中。类型库包含的信息有接口、方法和参数的名称和ID等。而在.NET中,所有这些信息都可以存储在程序集中,如第12章和第16章所述。COM
5、存在的问题是,类型库是不能扩展的。在C+中,IDL(接口定义语言)文件用于描述接口和方法。其中一些IDL修饰符不在类型库中,因为Visual Basic(和负责开发类型库的Visual Basic小组)不能使用这些IDL修饰符。而在.NET中,不存在这个问题,因为.NET元数据可以使用定制特性来扩展。因此,一些COM组件有类型库,而其他COM组件没有。如果没有类型库可用,就可以使用C+头文件来描述接口和方法。在.NET中,使用带有类型库的COM组件是比较容易的,也可使用不带类型库的COM组件。在这种情况下,必须使用C#代码重新定义COM接口。23.1.2 释放内存在.NET中,内存的释放是由垃
6、圾收集器完成的。这完全不同于COM,COM依赖的是引用数。接口IUnknown是每个COM对象必须实现的一个接口,它提供了3个方法。其中两个方法与引用数有关。如果需要另一个接口指针,客户程序就必须调用方法AddRef(),这个方法会递增引用数。方法Release()会递减引用数,如果所得的引用数是0,就销毁对象,释放内存。23.1.3 接口接口是COM的核心,它区分了在客户程序和对象之间使用的契约和实现方式。接口(契约)定义了由组件提供的方法,可以由客户程序使用。而在.NET中,接口也有非常重要的作用。COM有3种接口类型:定制接口、分派接口(dispatch interface)和双重接口。
7、1. 定制接口定制接口派生自接口IUnknown。定制接口定义了虚拟表(vtable)中的方法顺序,所以客户程序可以直接访问接口的方法。这也表示在开发阶段客户程序需要知道虚拟表,因为方法的绑定是使用内存地址进行的。因此,定制接口不能由脚本客户程序使用。图23-1显示了定制接口IMath的虚拟表,除了接口IUnknown的方法之外,该接口还提供了方法Add()和Sub()。图 23-12. 分派接口因为脚本客户程序(和早期的Visual Basic客户程序)不支持定制接口,所以需要另外一种接口类型,而在分派接口中,可用于客户程序的接口总是IDispatch接口。IDispatch接口派生自IUn
8、known接口,除了接口IUnknown的方法之外,它还提供了4个方法,其中两个比较重要的方法是GetIDsOfNames()和Invoke()。如图23-2所示,在分派接口中需要两个表。第一个表把方法或特性名映射到分派ID(dispatch id),第二个表把分派ID映射到方法或特性的实现代码。图 23-2在客户程序调用组件中的方法时,要先调用方法GetIDsOfNames(),并给它传送要调用的方法名。方法GetIDsOfNames()会查找名称-ID表,返回分派ID,客户程序再使用这个ID调用方法Invoke()。注意:通常,IDispatch接口的两个表存储在类型库中,但这不是必需的,
9、一些组件把这两个表存储在其他地方。3. 双重接口可以想像,分派接口比定制接口慢得多。另一方面,脚本客户程序不能使用定制接口。双重接口可以解决这个问题。如图23-3所示,双重接口派生自IDispatch接口,但提供了可以在虚拟表中直接使用的接口方法。脚本客户程序可以使用IDispatch接口调用方法,而了解虚拟表的客户程序可以直接调用方法。图 23-34. 强制类型转换和QueryInterface如果.NET类实现多个接口,就可以进行强制类型转换,得到一个接口或另一个接口。而在COM中,接口IUnknown通过方法QueryInterface()提供了类似的机制。如上一节所述,接口IUnkno
10、wn是其他接口的基础接口,所以可以以任何方式使用方法QueryInterface()。23.1.4 方法绑定客户程序映射方法的方式是用早期绑定和后期绑定来定义的。后期绑定表示要调用的方法是在运行期间确定的。.NET使用System.Reflection命名空间来实现后期绑定(参阅第12章)。COM使用上面讨论的IDispatch接口进行后期绑定。后期绑定可以使用分派接口和双重接口来实现。在COM中,早期绑定有两个不同的选项。早期绑定的一种方式也称为虚拟表绑定,它直接使用虚拟表,可以通过定制接口和双重接口来实现。早期绑定的第二种方式也称为id绑定。其中分派id存储在客户程序代码中,在运行期间只需
11、要调用一次Invoke()。GetIdsOfNames()方法在设计期间调用。对于这种客户程序,记住不必改变分派id是非常重要的。23.1.5 数据类型对于双重接口和分派接口,COM能使用的数据类型被局限于一个自动兼容的数据类型列表。接口IDispatch的Invoke()方法接收VARIANT数据类型的数组。VARIANT是许多不同数据类型的组合,例如BYTE、SHORT、LONG、FLOAT、DOUBLE、BSTR、IUnknown*、IDispatch*等。VARIANT在Visual Basic中很容易使用,但在C+中使用时就比较复杂了。在.NET中,使用Object类代替了VARIA
12、NT。在定制接口中,C+能使用的所有数据类型也可用于COM。但是,使用这个组件的客户程序只能采用某些语言来编程。23.1.6 注册.NET区分了私有程序集和共享程序集,详见第16章。而在COM中,所有的组件都进行了注册配置,是全局可用的。所有的COM对象都有一个唯一的标识符,该标识符由一个128位的数字组成,也称为类ID(CLSID)。创建COM对象时,COM API调用CoCreateInstance()会在注册表中查找CLSID和DLL或EXE的路径,然后加载DLL或启动EXE,并实例化组件。这个128位数字不容易记忆,所以许多COM对象还有一个prog id,该id很容易记忆,例如Exc
13、el.Application就映射到一个CLSID。除了CLSID之外,COM对象还为每个接口和类型库指定了一个唯一的标识符(IID和typelib id)。本章的后面将详细讨论注册表中的信息。23.1.7 线程COM使用了单元模型,这样程序员就不必考虑线程问题了。但是,这也增加了复杂性。不同的操作系统版本添加了不同的单元类型。本节讨论单线程单元和多线程单元。注意:.NET中的线程详见第18章。1. 单线程单元单线程单元(STA)是在Windows NT 3.51中引入的。在STA中,只允许一个线程(创建实例的线程)访问组件。但是,在一个进程中允许有多个单线程单元,如图23-4所示。图 23-
14、4在图23-4中,里面带棒棒糖的矩形表示COM组件。组件和线程(弯曲箭头)包含在单元中。外部的矩形表示进程。在STA中,不需要考虑多个线程访问实例变量的问题,因为这种保护是由COM特性实现的,只有一个线程可以访问组件。COM对象在编程时不是线程安全的,因此STA需要在注册表中把注册键ThreadingModel设置为Apartment。2. 多线程单元Windows NT 4.0引入了多线程单元(MTA)的概念。在MTA中,多个线程可以同时访问组件。图23-5显示了带一个MTA和两个STA的进程。COM对象在编程时是线程安全的,因此MTA需要在注册表中把键ThreadingModel设置为Fr
15、ee。值Both用于不考虑单元类型的线程安全的COM对象。注意:Vasual Basic 6.0不支持多线程单元。图 23-523.1.8 错误处理在.NET中,错误是通过抛出异常来生成的。在旧COM技术中,错误是通过方法返回HRESULT值来定义的。HRESULT的值是S_OK,表示方法成功。如果COM组件提供了详细的错误消息,COM组件就实现接口ISupportErrorInfo,该接口不但提供了错误消息,还提供了帮助文件的链接、错误源,在方法返回时还会返回一个错误信息对象。在.NET中,实现接口ISupportErrorInfo的对象会自动映射到详细的错误信息和一个.NET异常。提示:跟
16、踪和记录错误的内容详见第17章。23.1.9 事件处理.NET用C#关键字event和delegate提供了事件处理机制(参阅第7章)。第37章讨论了可用于.NET Remoting的事件处理机制。图23-6显示了COM的事件处理结构。在COM事件中,组件必须实现接口Iconnection- PointContainer和另一个实现接口IConnectionPoint的连接点对象(CPO)。在图23-6中,组件还定义了一个由CPO调用的输出接口ICompletedEvents。客户程序必须在sink对象中实现这个输出接口,而sink对象本身是一个COM对象。在执行过程中,客户程序在服务器中查询
17、接口IConnectionPointContainer。通过这个接口,客户程序让CPO执行方法FindConnectionPoint(),获得指向所返回的IConnectionPoint的指针。客户程序再使用这个接口指针调用Advise()方法,并把指向sink对象的指针传送给服务器。接着,组件就可以在客户程序的sink对象中调用方法了。客户程序服务器图 23-6本章的后面将讨论.NET事件和COM事件如何映射,让.NET客户程序处理COM事件,COM对象处理.NET事件。23.2 编组从.NET传送给COM组件和其他工具的数据必须转换为相应的表示法,这个机制也称为编组(marshaling)
18、。具体的转换过程取决于所传递数据的类型。这里必须区分blittable和non-blittable数据类型。blittable数据类型在.NET和COM中有相同的表示法,不需要转换。简单的数据类型如byte、short、int和long,仅包含这些简单数据类型的类和数组都属于blittable数据类型。blittable类型的数组必须是一维的。non-blittable数据类型需要进行转换。表23-1列出了一些non-blittable的COM数据类型及其对应的.NET数据类型。non-blittable数据类型需要转换,所以需要更多的开销。表 23-1COM数据类型.NET数据类型SAFEA
19、RRAYArrayVARIANTObjectBSTRStringIUnknown*,IDispatch*Object23.3 在.NET客户程序中使用COM组件要理解.NET应用程序如何使用COM组件,首先必须创建COM组件。创建COM组件不能使用C#或Visual Basic 2005,而应使用Visual Basic 6或C+(或其他支持COM的语言)。本章使用Active Template Library(ATL)和C+。注意:使用C#或Visual Basic 2005可以创建.NET组件,通过一个封装器就可以把该.NET组件用作COM对象,而封装器是真正的COM组件。封装在COM组件
20、中的.NET组件由.NET客户程序通过COM交互操作功能来使用是没有意义的。因为本书讲述的不是COM,所以不讨论代码的各个方面,只讨论建立示例所需要的代码。23.3.1 创建COM组件要用ATL和C+创建COM组件,先创建一个新的ATL项目。选择File | New | Project后,就会在Visual C+ Projects组中看到ATL Project向导。把名称设置为COMServer。在Application Settings中,选择Dynamic Link Library,再按下Finish。ATL Project向导刚才已为服务器创建了基础代码。还需要一个COM对象。在Solu
21、tion Explorer中添加一个类,选择ATL Simple Object。在打开的对话框中,为Short name字段输入COMDemo。其他字段都是自动填充的,但把接口名改为IWelcome,如图23-7所示。单击Finish为类和接口创建基本代码。图 23-7COM组件提供了两个接口,所以可以看到QueryInterface()方法是如何在.NET中映射的。COM组件还提供了3个简单的方法,所以我们可以看到交互操作是如何进行的。在Class视图中,选择接口IWelcome,添加方法Greeting(),如图23-8所示,该方法有3个参数:HRESULT Greeting(in BST
22、R name, out, retval BSTR* message);图 23-8IDL文件COMDemo.idl给COM定义了接口。向导在文件COMDemo.idl中生成的代码如下所示。唯一标识符uuid会有所不同。IWelcome接口定义了方法Greeting()。关键字_interface之前的方括号定义了接口的一些特性。uuid定义了接口的ID,dual标识了接口的类型。object,uuid(615B801E-3A5C-44EA-913B-8C8F53BBFB3F), dual,nonextensible,helpstring(IWelcome Interface),pointer_
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- c# 2005 net30 高级 编程 23 COM 操作性
限制150内