第十三讲:关于.NET组件

1/5/2008来源:Java教程人气:5011


  .NET组件是什么
  组件的定义有多种,但最常见有几种。组件是可互换的软件部分,它既是工业化系统的产物,也是工业第系统的动力。在.NET平台的组件层中,组件是以Assemblies的形式创建的。
  .NET平台创建了组件,并将组件作为其基本的元素。从本质上看,.NET平台组件是一个用任何.NET语言以插件形式开发的可互换的软件部件,它可以与其他应用程序实现互操作。使用COM+服务的.NET组件被称作服务化组件,以示与.NET中标准的可治理组件的区别。
  强命名.NET组件
  下面我们将讨论强命名组合体(.NET组件)是什么。当开发可配置类时,它必须被编译。在编译代码后,有二方面的事情需要考虑。第一,COM+集成要求被编译的组合体必须被强命名。我们必须通过运行被称为sn.exe的强命名工具生成一个密码,以生成一个强命名的组合体。一旦编译了强命名的组合体,必须使用System.Reflection名字空间中一个被称作AssemblyKeyFileAttribute的组合体级的属性调用存储在文件中的该密码。
  #using
  using System;
  using System::EnterPRiseServices;
  using System::Reflection;
  
  [assembly: applicationName("FirstApp")]
  [assembly: ApplicationActivation(ActivationOption.Library)]
  
  // AssemblyKeyFile属性调用由sn.exe生成的密码文件,组合体将拥有强命名
  [assembly: AssemblyKeyFile("thiskeyfile")]
  
  namespace ESExample
  {
  ???
  }
  
  第二,在编译强命名的组合体时,必须调用输出System.EnterpriseServices名字空间中类型的组合体━━System.EnterpriseServices.dll。下面是生成密码和编译可配置类的命令:
  sn -k thiskeyfile
  Cl /out:ThisExample.dll /t:library
  /r:System.EnterpriseServices.dll FirstCfgClass.cpp
  
  在COM+中注册.NET组件
  COM+有二种注册方式:动态方式和手工方式。这二种方式都相当简单,但对于本例,动态方式是合适的。动态注册方式还有一些要求:
  1、组合体必须是强命名的。
  2、组合休可以不在全局性的组合体缓冲区中。
  3、组合体必须被可治理的(.NET)客户端使用。
  4、组合体激活类型必须是Library。
  初看起来,似乎限制相当严格,但其实它包括多种情况。读者一定在想,激活类型必须是Library,但还没有创建过Library COM+组件呢。在.NET中,服务组件的客户端在同一台计算机上,或者客户端将远程访问代理应用程序,以访问COM+组件。因此,纯.NET解决方案将在大多数情况下使用动态注册方式。
  在客户端第一次实例化服务化组件时,就会进行动态注册,而且对于每个版本的组合体而言,只会注册一次。我们需要注重COM+目录更新和组件第一次被访问之间在时间上的滞后。代码将跟踪内存中对象的数量以及在一定的活动期间及其之后仍然有多少对象仍然是活动的。
  注重,在对对象进行初次调用后,系统中存在一个有5个对象的缓冲池。似乎是一旦一个对象被实例化,在有方法被调用之前,它一直是活动的。一旦有方法被调用,该对象就只在调用期间是活动的。这也提醒我们,在预备使用对象之前,尽量不要去招惹它们。
  移植的策略
  在决定将部分或全部现有的应用软件移植到.NET环境中,就需要决定哪种移植方法最适合你。本篇文章介绍了水平移植和垂直移植二种应用软件的移植方式。
  水平移植和垂直移植
  水平移植是指取代应用程序中的全部一个层。例如,可以选择取代基于Web的表示层中的asp代码或选择取代中间层中的COM代码。垂直移植指的是替换一个应用程序中所有n层中的一部分。
  组件设计
  本篇文章提出了一些与向.NET/COM移植和组件设计互操作性问题方面的普遍原则。通过互操作层在.NET和 COM环境之间进行互操作时,CCW或RCW(依据调用的方向而不同)必须在二个环境之间的调用栈中对数据进行转化,有些数据类型无需转换。包括整型、长整型和浮点型数据类型在内的通用性数据无需转换,而非通用性数据则需要转换。
  Visual Basic的BSTR是非通用性数据类型的一个例子。在向.NET移植应用程序之前,应该在可治理性和非可治理性代码之间尽量少地使用非通用性数据类型,原因是相关的转换代价将影响到应用程序的性能。
  通用数据类型
  大多数的数据类型在可治理性和非可治理性内存中的表示都相同,互操作层无需作非凡的处理,由于在可治理和非可治理代码之间无需转换,因此这些数据类型被称作通用类型。整型和浮点型数据是通用类型数据类型,由通用型数据类型组成的数组和结构也是通用型数据类型。
  非通用数据类型
  非通用数据类型在可治理和非可治理语言中的表示是不同的,由于当在可治理性和非可治理性代码之间进行互操作时,它们要求互相转换,因此被称作非通用型数据类型。例如,由于有几种不同的非可治理性表示,其中的一些可能需要进行转换,因此,可治理的字符串是非通用型数据类型。字符串、日期、对象是非通用型数据类型的例子,在执行互操作时,它们都需要转换。
  现有的COM组件和可治理客户端
  在向.NET平台移植应用程序时,需要考虑现有的COM组件所使用的界面。尽管会不再使用现有的COM客户端,仍然会在.NET客户端中使用现有的COM组件。因此,在设计界面时,应该如何既考虑到二种环境中现有的组件也要考虑到未来的可治理客户端。
  在向.NET移植组件时,需要使用tlbimp工具自动地生成一个应用程序的RCW。缺省情况下,RCW使用与现有组件相同的界面(通过定义相同和属性和方法)。在许多情况下,传统的COM类型的界面并不是天生地从可治理性代码中使用的。可治理性代码开发人员将能够充分地利用下面的特性:
  ·参数化的构造器
  ·继续
  ·静态方法
  我们应当考虑编写一个COM对象使用的自定义包装类,向可治理客户端提供这些能力,创建更适合可治理代码环境的界面。包装类在内部使用COM组件的RCW,并代理对现有COM组件的大多数调用,一些调用能够完成更复杂的数据类型转换工作,例如,ADO .NET数据集和ADO记录集之间的映射。此后,我们可以将更多的功能从COM组件转移到包装类中,而不会影响可治理代码。在决定是使用RCW或自定义的可治理性包装类时,有许多因素需要考虑。需要注重的是,TlbImp工具能够将COM类型库转换为.NET架构元数据。一旦类型库被转换为元数据,可治理客户端可以无缝地调用COM类型。为了简化使用,我们总是在类型库中提供类型信息。
  假如组件有大量的已经习惯了现有对象模型的客户端,创建RCW和使用现有的界面就是一个比较合适的策略。Excel中的对象模型被使用VBA的Excel开发人员广泛使用,对象模型是高度结构化的,并且能够很好地映射由Excel提供的特性和用户界面。客户非常熟悉现有的对象模型,假如对象模型发生大幅度的变化,用户就需要进行大量的练习。在本例中,使用标准的RCW可能是合适的。
  在编写COM界面的自定义的可治理包装类时,这些界面将被通过RCW从可治理性代码中调用,每个属性调用都需要有互操作层的参与,会带来一定的代价。对于一个只作很少工作的简单界面来说,互操作造成的代价将是30-50条汇编指令。对于完成大量工作的方法而言,这点代价是微不足道的,但对于一个简单的属性访问而言,这一代价还是太大了。
  假如在不久之后要将COM组件的客户端移植到.NET平台上,在考虑编写自定义的可治理包装类时,应当将界面的功能由COM组件转到包装类中,否则,可以在COM对象中实现界面,并被代理到可治理的包装类中。
  通过重新安排代码,我们能够使互操作层的代价最小,并拥有一个从可治理代码到对象的最简单自然的界面。编写自定义的可治理包装类的带来的另一个好处是可以移动远程对象的分界线。
  类的界面的实现
  在可治理代码中,类的界面的定义并不是显性的,该界面包含适用于.NET对象使用的所有公共方法、属性、域和事件。它可以是一个双重或者仅起调度作用的界面,类界面的名字为.NET类名字前加一下划线。例如,对于Mammals而言,类界面的名字是_Mammals。对于派生类而言,类界面也需要实现基本类所有的公共方法、域和属性,派生类还实现每个基本类的界面。例如,Mammals类扩展了类MammalMainclass,.NET对象向COM客户端提供三个名字为_Mammals、_MammalMainclass和_Object的界面。
  COM客户端能够获得名字为Mammals的类界面,该界面定义在由类型库输出向导(TlbeXP.exe)工具生成的类型库中。假如Mammals类实现一个或多个界面,这些界面将出现在coclass中。
  [odl, uuid(0000...0000), hidden, dual, nonextensible, oleautomation]
  interface _Mammals : IDispatch
  {
  [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
  pRetVal);
  [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
  VARIANT_BOOL* pRetVal);
  [id(0x60020002)] HRESULT GetHashCodes([out, retval] short* pRetVal);
  [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
  [id(0x6002000d)] HRESULT EatIt();
  [id(0x6002000e)] HRESULT GetBreathe();
  [id(0x6002000f)] HRESULT GoSleep();
  }
  [uuid(0000...0000)]
  coclass Mammals
  {
  [default] interface _Mammals;
  }
  类界面生成是可选的。假如没有选择其他选项,COM互操作层为每个输出到类库中的类生成一个只起调度作用的界面,通过在类中添加ClassInterfaceAttribute属性,我们可以中止或修改这一界面的自动创建。尽管类界面能够使我们无须向COM提供可治理