




已阅读5页,还剩34页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第23章 COM的互操作性230第 章 COM的互操作性如果您在学习.NET之前编写过Windows程序,通常没有时间和资源用.NET再重新编写以前的程序。有时重写代码有助于做一些修订,重新思考应用程序的体系架构,从长远来看,还有助于提高效率,更便于用新技术添加新特性。但是,我们不会为使用一种新技术而重写已有的代码。我们本来有数千行可运行的代码,重写它们需要的精力太多,还不如把它们迁移到托管的环境中。这也同样适用于Microsoft。在命名空间System.DirectoryService中,Microsoft并没有重新编写COM对象来访问有层次的数据存储,这个命名空间中的类实际上是访问ADSI COM对象的包装器。System.Data.OleDb命名空间也是这样,由这个命名空间中的类所使用的OLE DB提供程序包含相当复杂的COM接口。我们自己的解决方案也会面临相同的问题。如果在.NET应用程序中要使用已有的COM对象,或者要编写在旧COM客户程序中使用的.NET组件,就应使用本章介绍的COM互操作性。如果没有要与应用程序集成的COM组件,或旧COM客户程序要使用一些.NET组件,就应跳过本章。本章主要内容如下: COM和.NET技术 在.NET应用程序中使用COM对象 在COM客户程序中使用.NET组件 调用本地方法的Platform Invoke(平台调用)与其他章节一样,本章的示例代码也可以从Wrox网站上下载。23.1 .NET和COMCOM是.NET以前的技术。COM定义了一个组件模型,在该模型中,组件可以用不同的编程语言编写。用C+编写的组件可以在VB客户程序中使用。组件还可以在本地的进程中使用,跨进程使用或在网络上使用。看起来是不是很熟悉?当然,.NET的目标也是这样。但这些目标的实现方式是不同的。COM概念使用起来越来越复杂,已经不能扩展了。.NET达到了与COM类似的目标,但引入了新概念,实现起来更容易。即使到了今天,使用COM和.NET交互操作的主要问题是要理解COM。是COM客户程序使用.NET组件,还是.NET应用程序使用COM组件并不重要,而是必须理解COM。所以这里首先比较COM和.NET。如果您已经熟练掌握了COM技术,本节将是COM知识的复习。否则,您将学习到COM的概念 现在是使用.NET 我们不再需要在日常事务中处理它了。但是,在把COM技术集成到.NET应用程序中时,COM的问题仍旧存在。COM和.NET有许多类似的概念和不同的解决方案。下面将讨论: 元数据 释放内存 接口 方法绑定 数据类型 注册 线程 错误处理 事件处理23.1.1 元数据在COM中,组件的所有信息都存储在类型库中。类型库包含的信息有接口、方法和参数的名称和ID等。而在.NET中,所有这些信息都可以存储在程序集中,如第12章和第16章所述。COM存在的问题是,类型库是不能扩展的。在C+中,IDL(接口定义语言)文件用于描述接口和方法。其中一些IDL修饰符不在类型库中,因为Visual Basic(和负责开发类型库的Visual Basic小组)不能使用这些IDL修饰符。而在.NET中,不存在这个问题,因为.NET元数据可以使用定制特性来扩展。因此,一些COM组件有类型库,而其他COM组件没有。如果没有类型库可用,就可以使用C+头文件来描述接口和方法。在.NET中,使用带有类型库的COM组件是比较容易的,也可使用不带类型库的COM组件。在这种情况下,必须使用C#代码重新定义COM接口。23.1.2 释放内存在.NET中,内存的释放是由垃圾收集器完成的。这完全不同于COM,COM依赖的是引用数。接口IUnknown是每个COM对象必须实现的一个接口,它提供了3个方法。其中两个方法与引用数有关。如果需要另一个接口指针,客户程序就必须调用方法AddRef(),这个方法会递增引用数。方法Release()会递减引用数,如果所得的引用数是0,就销毁对象,释放内存。23.1.3 接口接口是COM的核心,它区分了在客户程序和对象之间使用的契约和实现方式。接口(契约)定义了由组件提供的方法,可以由客户程序使用。而在.NET中,接口也有非常重要的作用。COM有3种接口类型:定制接口、分派接口(dispatch interface)和双重接口。1. 定制接口定制接口派生自接口IUnknown。定制接口定义了虚拟表(vtable)中的方法顺序,所以客户程序可以直接访问接口的方法。这也表示在开发阶段客户程序需要知道虚拟表,因为方法的绑定是使用内存地址进行的。因此,定制接口不能由脚本客户程序使用。图23-1显示了定制接口IMath的虚拟表,除了接口IUnknown的方法之外,该接口还提供了方法Add()和Sub()。图 23-12. 分派接口因为脚本客户程序(和早期的Visual Basic客户程序)不支持定制接口,所以需要另外一种接口类型,而在分派接口中,可用于客户程序的接口总是IDispatch接口。IDispatch接口派生自IUnknown接口,除了接口IUnknown的方法之外,它还提供了4个方法,其中两个比较重要的方法是GetIDsOfNames()和Invoke()。如图23-2所示,在分派接口中需要两个表。第一个表把方法或特性名映射到分派ID(dispatch id),第二个表把分派ID映射到方法或特性的实现代码。图 23-2在客户程序调用组件中的方法时,要先调用方法GetIDsOfNames(),并给它传送要调用的方法名。方法GetIDsOfNames()会查找名称-ID表,返回分派ID,客户程序再使用这个ID调用方法Invoke()。注意:通常,IDispatch接口的两个表存储在类型库中,但这不是必需的,一些组件把这两个表存储在其他地方。3. 双重接口可以想像,分派接口比定制接口慢得多。另一方面,脚本客户程序不能使用定制接口。双重接口可以解决这个问题。如图23-3所示,双重接口派生自IDispatch接口,但提供了可以在虚拟表中直接使用的接口方法。脚本客户程序可以使用IDispatch接口调用方法,而了解虚拟表的客户程序可以直接调用方法。图 23-34. 强制类型转换和QueryInterface如果.NET类实现多个接口,就可以进行强制类型转换,得到一个接口或另一个接口。而在COM中,接口IUnknown通过方法QueryInterface()提供了类似的机制。如上一节所述,接口IUnknown是其他接口的基础接口,所以可以以任何方式使用方法QueryInterface()。23.1.4 方法绑定客户程序映射方法的方式是用早期绑定和后期绑定来定义的。后期绑定表示要调用的方法是在运行期间确定的。.NET使用System.Reflection命名空间来实现后期绑定(参阅第12章)。COM使用上面讨论的IDispatch接口进行后期绑定。后期绑定可以使用分派接口和双重接口来实现。在COM中,早期绑定有两个不同的选项。早期绑定的一种方式也称为虚拟表绑定,它直接使用虚拟表,可以通过定制接口和双重接口来实现。早期绑定的第二种方式也称为id绑定。其中分派id存储在客户程序代码中,在运行期间只需要调用一次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类代替了VARIANT。在定制接口中,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很容易记忆,例如Excel.Application就映射到一个CLSID。除了CLSID之外,COM对象还为每个接口和类型库指定了一个唯一的标识符(IID和typelib id)。本章的后面将详细讨论注册表中的信息。23.1.7 线程COM使用了单元模型,这样程序员就不必考虑线程问题了。但是,这也增加了复杂性。不同的操作系统版本添加了不同的单元类型。本节讨论单线程单元和多线程单元。注意:.NET中的线程详见第18章。1. 单线程单元单线程单元(STA)是在Windows NT 3.51中引入的。在STA中,只允许一个线程(创建实例的线程)访问组件。但是,在一个进程中允许有多个单线程单元,如图23-4所示。图 23-4在图23-4中,里面带棒棒糖的矩形表示COM组件。组件和线程(弯曲箭头)包含在单元中。外部的矩形表示进程。在STA中,不需要考虑多个线程访问实例变量的问题,因为这种保护是由COM特性实现的,只有一个线程可以访问组件。COM对象在编程时不是线程安全的,因此STA需要在注册表中把注册键ThreadingModel设置为Apartment。2. 多线程单元Windows NT 4.0引入了多线程单元(MTA)的概念。在MTA中,多个线程可以同时访问组件。图23-5显示了带一个MTA和两个STA的进程。COM对象在编程时是线程安全的,因此MTA需要在注册表中把键ThreadingModel设置为Free。值Both用于不考虑单元类型的线程安全的COM对象。注意:Vasual Basic 6.0不支持多线程单元。图 23-523.1.8 错误处理在.NET中,错误是通过抛出异常来生成的。在旧COM技术中,错误是通过方法返回HRESULT值来定义的。HRESULT的值是S_OK,表示方法成功。如果COM组件提供了详细的错误消息,COM组件就实现接口ISupportErrorInfo,该接口不但提供了错误消息,还提供了帮助文件的链接、错误源,在方法返回时还会返回一个错误信息对象。在.NET中,实现接口ISupportErrorInfo的对象会自动映射到详细的错误信息和一个.NET异常。提示:跟踪和记录错误的内容详见第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对象。在执行过程中,客户程序在服务器中查询接口IConnectionPointContainer。通过这个接口,客户程序让CPO执行方法FindConnectionPoint(),获得指向所返回的IConnectionPoint的指针。客户程序再使用这个接口指针调用Advise()方法,并把指向sink对象的指针传送给服务器。接着,组件就可以在客户程序的sink对象中调用方法了。客户程序服务器图 23-6本章的后面将讨论.NET事件和COM事件如何映射,让.NET客户程序处理COM事件,COM对象处理.NET事件。23.2 编组从.NET传送给COM组件和其他工具的数据必须转换为相应的表示法,这个机制也称为编组(marshaling)。具体的转换过程取决于所传递数据的类型。这里必须区分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数据类型SAFEARRAYArrayVARIANTObjectBSTRStringIUnknown*,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组件中的.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对象。在Solution 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 BSTR 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_default(unique)interface IWelcome : IDispatchid(1), helpstring(method Greeting) HRESULT Greeting(in BSTR name, out,retval BSTR* message);IDL文件还定义了类型库的内容,它是执行IWelcome接口的COM对象(coclass):uuid(1CE0DFFF-ADA8-47DD-BA06-DDD89C584242),version(1.0),helpstring(COMServer 1.0 Type Library)library COMServerLibimportlib(stdole2.tlb);uuid(AB13E0B8-F8E1-497E-985F-FA30C5F449AA),helpstring(COMDemo Class)coclass COMDemodefault interface IWelcome;提示:在定制特性中,可以改变由.NET包装器类生成的类和接口的名称。只需给特性custom添加标识符0F21F359-AB84-41e8-9A78-36D110E6D2F9,并给.NET中显示的内容指定名称即可。在IWelcome接口的头文件部分,添加带有相同标识符和名称Wrox.ProCSharp. 的定制特性COMInterop.Server.IWelcome。给类CCOMDemo添加带有相应名称的同一特性。object,uuid(615B801E-3A5C-44EA-913B-8C8F53BBFB3F),dual,nonextensible,helpstring(IWelcome Interface),pointer_default(unique),custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.IWelcome)interface IWelcome : IDispatchid(1), helpstring(method Greeting) HRESULT Greeting(in BSTR name,out,retval BSTR* message);library COMServerLibimportlib(stdole2.tlb);uuid(AB13E0B8-F8E1-497E-985F-FA30C5F449AA),helpstring(COMDemo Class)custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.COMDemo),coclass COMDemodefault interface IWelcome;现在给文件COMDemo.idl添加第二个接口。可以把IWelcome接口的头文件部分复制到新接口IMath的头文件部分,但要确保修改用uuid关键字定义的唯一标识符。可以用实用工具guidgen生成这样一个ID。接口IMath提供了两个方法Add()和Sub()。/ IMathobject,uuid(2158751B-896E-461d-9012-EF1680BE0628), dual,nonextensible,helpstring(IMath Interface),pointer_default(unique),custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.IMath)interface IMath : IDispatchid(1) HRESULT Add(in LONG val1, in LONG val2, out, retval LONG* result);id(2) HRESULT Sub(in LONG val1, in LONG val2, out, retval LONG* result);类CCOMDemo必须修改,使之实现接口IWelcome和IMath,接口IWelcome是默认接口。uuid(AB13E0B8-F8E1-497E-985F-FA30C5F449AA),helpstring(COMDemo Class),custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.COMDemo) coclass COMDemodefault interface IWelcome;interface IMath;现在可以把注意力从IDL文件转向C+代码了。在COMDemo.h文件中,包含了COM对象的类定义。CCOMDemo类使用多重继承机制,继承了模板类CComObjectRootEx、CComCoClass和IDisplatchImpl。CComObjectRootEx提供了IUnknown接口功能的执行代码,如AddRef和Release。CComCoClass创建了一个实例工厂,来实例化模板变元的对象,这里是CComDemo。IDispatchImpl提供了IDispatch接口中方法的执行代码。利用BEGIN_COM_MAP和END_COM_MAP中的宏,创建一个映射,定义COM类实现的所有COM接口。这个映射由QueryInterface方法的执行代码使用。class ATL_NO_VTABLE CCOMDemo :public CComObjectRootEx,public CComCoClass,public IDispatchImplpublic:CCOMDemo()DECLARE_REGISTRY_RESOURCEID(IDR_COMDEMO)BEGIN_COM_MAP(CCOMDemo)COM_INTERFACE_ENTRY(IWelcome)COM_INTERFACE_ENTRY(IDispatch)END_COM_MAP()DECLARE_PROTECT_FINAL_CONSTRUCT()HRESULT FinalConstruct()return S_OK;void FinalRelease()public:STDMETHOD(Greeting)(BSTR name, BSTR* message);OBJECT_ENTRY_AUTO(_uuidof(COMDemo), CCOMDemo)有了这个类定义,还必须添加第二个接口IMath,以及用IMath接口定义的方法:class ATL_NO_VTABLE CCOMDemo :public CComObjectRootEx,public CComCoClass,public IDispatchImplpublic IDispatchImplpublic:CCOMDemo()DECLARE_REGISTRY_RESOURCEID(IDR_COMDEMO)BEGIN_COM_MAP(CCOMDemo)COM_INTERFACE_ENTRY(IWelcome)COM_INTERFACE_ENTRY(IMath)COM_INTERFACE_ENTRY2(IDispatch, IWelcome)END_COM_MAP()DECLARE_PROTECT_FINAL_CONSTRUCT()HRESULT FinalConstruct()return S_OK;void FinalRelease()public:STDMETHOD(Greeting)(BSTR name, BSTR* message);STDMETHOD(Add)(long val1, long val2, long* result);STDMETHOD(Sub)(long val1, long val2, long* result);OBJECT_ENTRY_AUTO(_uuidof(COMDemo), CCOMDemo)现在可以在文件COMDemo.cpp中用下面的代码执行3个方法了。CComBSTR是一个很容易处理BSTR的ATL类。在Greeting()方法中,只返回一个欢迎信息,并把第一个参数传入的名称添加到返回的信息中。Add()方法把两个值加在一起,而Sub()方法进行减法操作,返回相减的结果。STDMETHODIMP CCOMDemo:Greeting(BSTR name, BSTR* message) CCOMBSTR tmp(Welcome, ); tmp.Append(name); *message = tmp; return S_OK;STDMETHODIMP CCOMDemo:Add(LONG val1, LONG val2, LONG* result) *result = val1 + val2; result S_OK;STDMETHODIMP CCOMDemo:Sub(LONG val1, LONG val2, LONG* result) *result = val1 - val2; return S_OK;现在就可以建立组件了。建立过程也是指在注册表中配置组件。23.3.2 创建Runtime Callable Wrapper现在可以在.NET中使用COM组件了。为此,必须创建一个Runtime Callable Wrapper (RCW)。使用RCW,.NET客户程序就可以使用.NET对象而不是COM组件,所以不需要处理COM特性,这是由包装器来处理的。RCW隐藏了IUnknown和IDispatch接口(如图23-9所示),并处理COM对象的引用数。RCW可以使用命令行实用工具tlbimp或Visual Studio来创建。启动命令:tlbimp COMServer.dll /out: Interop.COMServer.dll会创建文件Interop.COMServer.dll,其中包含带有包装器类的.NET程序集。在这个生成的程序集中,可以找到命名空间COMWrapper、类CCOMDemoCalss、接口CCOMDecmo、IMath和IWelcome。命名空间的名称可以使用实用工具tlbimp的选项来修改。选项/namespace允许指定不同的命名空间,/asmversion可以定义程序集的版本号。注意:这个命令行实用工具的另一个重要选项是/keyfile,它可以把一个强名赋给所生成的程序集。关于强名,详见第16章。图 23-9RCW还可以使用Visual Studio来创建。要创建一个简单的示例应用程序,应创建一个C#控制台项目。在Solution Explorer中,选择COM选项卡,向下滚动到COMServer 1.0 Type Library选项上,可以添加对COM服务器的引用,如图23-10所示。这里列出了在注册表中配置的所有COM对象。从列表中选择一个COM组件,创建一个带RCW类的程序集。23.3.3 使用RCW在创建好包装器类后,就可以为应用程序编写代码来实例化和访问组件了。由于在C+文件中有定制特性,所以RCW类生成的命名空间是Wrox.ProCSharp.COMInterop.Server。在上面的声明中添加这个命名空间和System.Runtime.InteropServices命名空间。在System.Runtime. InteropServices命名空间中,使用Marshal类可以释放COM对象。图 23-10using System;using System.Runtime.InteropServices;using Wrox.ProCSharp.COMInterop.Server;namespace Wrox.ProCSharp.COMInterop.Client class Program STAThread static void Main(string args) 现在可以像使用.NET类那样使用COM组件了。obj是COMDemo类型的变量,COMDemo是一个.NET接口,它提供了IWelcome和IMath接口的方法。还可以对特定的接口进行强制数据类型转换,例如IWelcome。有了声明为IWelcome类型的变量后,就可以调用方法Greeting()。注意:尽管COMDemo是一个接口,但可以实例化COMDemo类型的新对象。与一般的接口不同,可以对封装的COM接口进行这类操作。 COMDemo obj = new COMDemo(); IWelcome welcome = obj; Console.WriteLine(welcome.Greeting(Christian);如果对象(如本例所示)提供了多个接口,就可以声明其他接口的变量,通过使用简单的赋值语句和强制转换运算符,包装器类就可以通过QueryInterface()方法和COM对象返回第二个接口指针。使用math变量可以调用IMath接口的方法。 IMath math; math = (IMath)obj; int x = math.Add(4,5); Console.WriteLine(x);如果在垃圾收集器清理对象之前释放COM对象,静态方法Marshal.ReleaseComObject()就调用组件的Release()方法,这样组件就可以销毁它自己,并释放内存了。 Marshal.ReleaseComObject(math); 提示:前面提到,引用数为0时,就释放COM对象。Marshal.ReleaseComObject()会调用Release()方法给引用数减1。RCW仅调用一次AddRef()方法,来递增引用数,所以无论有多少对RCW的引用,调用一次Marshal.ReleaseComObject(),就足以释放对象了。在使用Marshal.ReleaseComObject()释放COM对象后,就不能使用引用该对象的变量了。在本例中,COM对象是使用math变量释放的。welcome变量也引用了COM对象,它不能在释放对象后使用。否则,就会生成一个InvalidComObjectException类型的异常。注意:COM对象在不再需要时就释放,这是很重要的。COM对象使用内置的内存堆,而.NET对象使用托管的内存堆。垃圾收集器只处理托管的内存。可以看出,有了RCW,就可以像使用.NET对象那样来使用COM组件。RCW的特殊情况是可交互操作的主程序集。23.3.4 可交互操作的主程序集可交互操作的主程序集是COM组件的供应商已经准备好的程序集。它可以更容易地使用COM对象。可交互操作的主程序集不同于自动生成的RCW。可交互操作的主程序集在目录Microsoft .NETPrimary Interop Assemblies中。它已经存在,用于.NET中的ADO。如果添加对COM库Microsoft ActiveX Data Objects 2.7 Library的引用,就不创建包装器类,因为可交互操作的主程序集已经存在;否则就引用可交互操作的主程序集。23.3.5 线程问题如本章前面所述,COM组件根据执行的线程安全与否,标记它所在的单元(STA或MTA)。但是,线程必须加入到单元中。添加线程的单元可以用STAThread和MTAThread特性定义,这两个特性可以应用于应用程序的方法Main()。STAThread特性表示线程加入STA。而特性MTAThread表示线程加入MTA。如果没有应用特性,默认情况下就加入MTA。还可以使用Thread类的ApartmentState特性编程设置单元状态。ApartmentState特性允许设置ApartmentState枚举中的一个值。ApartmentState枚举的值有STA和MTA(如果没有设置,就使用Unknown)。注意线程的单元状态只能设置一次。如果第二次设置它,就会忽略第二次的设置。提示:如果线程选择了组件所不支持的单元,该怎么办?COM运行库会自动创建COM组件的正确单元。但是,如果在调用组件的方法时跨越了单元边界,性能就会降低。23.3.6 添加连接点为了理解COM事件在.NET应用程序中的处理方式,首先必须扩展COM组件。使用特性在ATL类中执行COM事件类似于执行.NET中的事件,但功能不同。首先必须在接口定义文件COMDemo.idl中添加另一个接口。接口ICompletesEvents由客户程序(.NET应用程序)实现,由组件调用。在本例中,在完成了计算后,方法Completes()由组件调用。这个接口也称为输出接口。输出接口必须是分派接口或定制接口。所有的客户程序都支持分派接口。id为0F21F359-AB84-41e8-9A78-36D110E6D2F9的定制接口定义了在RCW中创建的接口名。输出接口还必须在coclass段中写入组件支持的接口,标记为source接口。library COMServerLibimportlib(stdole2.tlb);uuid(5CFF102B-0961-4EC6-8BB4-759A3AB6EF48),helpstring(_ICompletedEvents Interface),custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.ICompletedEvents),dispinterface _ICompletedEventsproperties:methods:id(1) void Completed(void);uuid(AB13E0B8-F8E1-497E-985F-FA30C5F449AA),helpstring(COMDemo Class)custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,Wrox.ProCSharp.COMInterop.Server.COMDemo),coclass COMDemodefault interface IWelcome;interface IMath;default, source dispinterface _ICompletedEvents;使用向导可以创建执行代码,将事件发送给客户程序。打开类视图,选择CComDemo类,打开弹出菜单,启动Implement Connection Point Wizard,如图23-11所示。给带连接点的执行代码选择源接口ICompletedEvents。图 23-11向导创建了代理类CProxy_ICompletedEvents,将事件发送给客户程序。另外,还修改了CCOMDemo类。这个类现在继承了IConnectionPointContainerImpl和代理类。IConnectionPoint- Container接口添加到接口映射中,连接点映射添加到源接口_ICompletedEvents中。class ATL_NO_VTABLE CCOMDemo :public CComObjectRootEx,public CComCoClass,public IDispatchImpl,public IDispatchImpl,public IConnectionPointContainerImpl,public CProxy_ICompletedEventspublic:/.BEGIN_COM_MAP(CCOMDemo)COM_INTERFACE_ENTRY(IWelcome)COM_INTERFACE_ENTRY(IMath)COM_INTERFACE_ENTRY2(IDispatch, IWelcome)COM_INTERFACE_ENTRY(IConnectionPointContainer)END_COM_MAP()/.public:BEGIN_CONNECTION_POINT_MAP(CCOMDemo)CONNECTION_POINT_ENTRY(_uuidof(_ICompletedEvents)END_CONNECTION_POINT_MAP();最后,在文件COMDemo.cpp的Add()和Sub()方法中调用代理类的方法Fire_Completed()。STDMETHODIMP CCOMDemo:Add(LONG val1, LONG val2, LONG* result) *result = val1 + val2;Fire_Completed(); result S_OK;STDMETHODIMP CCOMDemo:Sub(LONG val1, LONG val2, LONG* result) *result = val1 - val2;Fire_Completed(); return S_OK;在重新建立COM DLL之后,就可以把.NET客户程序改为使用这些COM事件,这与使用一般的.NET事件一样。static void Main()COMDemo obj = new COMDemo();IWelcome welcome = obj;Console.WriteLine(welcome.Greeting(Christian);obj.Completed +=new ICompletedEvents_CompletedEventHandler(delegateConsole.WriteLine(Calculation completed););IMath math = (IMath)welcome;int result = math.Add(3, 5);Console.WriteLine(result);Marshal.ReleaseComObject(math);可以看出,RCW提供了从COM事件到.NET事件的自动映射。可以在.NET客户程序中像使用.NET事件那样使用COM事件。23.3.7 在Windows窗体中使用ActiveX控件ActiveX控件是带有一个用户界面和许多可选COM接口的COM对象,这些COM接口可以处理用户界面,与容器进行交互操作。ActiveX控件可以被许多不同的容器使用,例如Internet Explorer、Word、Excel,用VB6、MFC(Microsoft Foundation Classes)或ATL(Active Template Library)编写的应用程序。Windows窗体应用程序是另一个可以管理ActiveX控件的容器。ActiveX控件可以像前面讨论的Windows窗体控件那样使用。1. ActiveX控件导入器与RCW一样,也可以为ActiveX控件创建包装器。ActiveX控件的包装器可以使用命令行实用工具Windows Forms ActiveX Control Importer(aximp.exe)来创建。这个实用工具会创建一个派生于基类System.Windows.Forms.AxHost的类,它用作包装器,以使用ActiveX控件。在Web窗体控
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 公立医院医生岗位职责及工作制度
- 中国照相机脚架行业市场发展前景及发展趋势与投资战略研究报告(2024-2030)
- 2025-2030年中国电镀试验室用电源项目投资可行性研究分析报告
- 2025年中国幼儿园露天游乐设备行业发展态势与投资价值分析报告
- 2024年中国低密度聚乙烯行业市场调查报告
- 中国惠州房地产行业市场深度研究及投资战略规划报告
- 2025年中国圆柱销行业市场发展前景及发展趋势与投资战略研究报告
- 中国手机支架行业市场发展现状及前景趋势与投资分析研究报告(2024-2030)
- 2025年中国新能源客车行业市场调查研究及发展战略规划报告
- 2025年中国微波通信设备市场行情动态分析及发展前景趋势预测报告
- 2025年陕西延长石油矿业有限责任公司招聘笔试参考题库含答案解析
- 天津市部分区2023-2024学年八年级下学期期末练习道德与法治试卷
- 2023年九年级中考数学高频考点突破-圆的切线的证明【含答案】
- 2023年内江市市中区财政局系统事业单位招聘笔试题库及答案解析
- 国际贸易实务全部资料课件
- 带状疱疹医学课件
- 全国卷高考标准语文答题卡作文纸3栏800字版
- IATF16949体系培训资料课件
- 事业单位招聘考试《工程建设管理专业知识》真题汇总及答案【含解析】
- 初一几何综合练习题
- 综合实践活动评价表完整
评论
0/150
提交评论