实现客户端
       当一个基于 CLR 组件服务类被编译和部署,你就需要在客户端调用它。长远来看,组件服务类没什么特别之处 ; 事实是使用 COM+ 运行时服务是不相关的。 这个代码展示了简单类早期的定义 MyTxCfgClass:
using ESExample;
 
public class Client
{
 public static void Main(string[] args)
 {
    MyTxCfgClass tcc = new MyTxCfgClass();
     ... // use object as desired
 }
}
       当然,与所有的 CLR 代码,客户端必须引用组件服务类程序集。
csc /r:ESExample.dll Client.cs
细微的复杂性
       这里你会看到,服务类通过 .NET CLR 实现 COM+ 是相当的容易。 System.EnterpriseServices 命名空间里的类提供了可以使用 COM+ 运行时服务的 API 。运行时服务本身没什么改变;他们工作的方式还是 COM+ 开发者熟知的。已经说了, 集成 COM+ CLR 没有涉及到如此细微的复杂性。 有几个问题是使得使用 CLR 语言写 COM+ 代码比我刚才提到的复杂的多。 有些问题会产生,因为 CLR 毕竟不是 COM 因为它可以做 COM 做不到的事情。这些问题是不能解决的 ; 你只要知道如何 CLR 的特性如静态方法和垃圾回收器 如何影响 COM+ 编程。
      
再学习这些问题前,你必须知道 CLR 的类和 and COM+ 上下文环境的关系。 首先,调用继承自 ServicedComponent 的类的实例都会被 COM+ 上下文环境边界侦听。这些对象称为上下文环境边界。调用没继承自 ServicedComponent 的类的实例就不会被上下文环境边界侦听。 这些对象称为 context-agile CLR 对象默认就是 context-agile 当你继承自 ServicedComponent 它们会变成 context-bound ( 这个与 .NET remoting 上下文环境没有关系,我下面会讲到。 )  9 说明的这个架构
9 上下文环境对象

     
非常有趣的是 CLR 对象可以实现类似 COM 对象和 COM+ 上下文环境交互这样的行为。通过 COM ,调用对象通常默认都会被侦听 ; 所有的对象都是 context-bound COM 对象是 context-agile 只有当它聚集了 freethreaded marshaler (FTM) 且不是第一个在 COM+ 上下文环境里 被创建的对象 ( 因为它不是可识别的对象 ) 这样情况下,调用不会被侦听。 新方法确保调用真的需要的时候被预处理和迟处理的好处是减少了侦听负荷。 特别地,如果组件服务类的实例返回了一个非组件服务类的实例 ( 例如一个 ADO.NET DataSet 对象 ) ,这个调用不会被侦听。 并且 DataSet 对象不需要做任何事情,它就是照常工作。
       第二个你应该知道的事情是, 除了减少真正需要的交叉上下文侦听外, CLR/COM+ 集成尽可能地避免了托管类型和自有类型间的转换。 来自托管的 CLR 代码到 COM 的调用是比较昂贵的。重要的部分就是类型的前后转换 大部分在 CLR strings COM BSTRs 之间。 请求穿过 COM+ 环境边界需要 调用一些固有的代码, 组件已经相当聪明可以避免同样来自 CLR 且处在同一进程里的调用者和被调用者之间的类型转换。 或许有一天所有的 COM+ 运行时服务都会用 CLR 语言重新实现,调转就不成问题了。 那时不管如何,这个优化都会使得 COM+ 侦听更加快速。
静态方法和属性
       现在你知道了 CLR 代码如何关联上下文环境, 思考这个问题:如果一个基于 CLR 组件服务类 包括静态方法和属性访问器, 它将在什么上下文里执行呢?答案是调用者。 这个看起来不是很直观, 但是它确实很有意义当你想静态成员不是对象定义且不许要在对象驻留的上下文里访问。例如,组件服务类在  10 有个静态方法, 2 和静态属性, 4 。这个代码会在调用者的上下文里执行。 调用实例的方法 1 或者属性 3 ,通常在对象的上下文里执行。
     
这时你或许想知道当你在属性里保存了一个对象的静态引用且你想在另外一个上下问里引用会发生什么事情。基于 COM COM+ 编程里, 在被成为静态属性的全局变量里保存了一个原始的对象引用, 会导致严重的错误因为你不可以把对象不加包装地从原来的上下文里带走。使用基于 CLR COM+ 代码, 这个就不是一个问题。 CLR 使用相当瘦的代理包装了每个组件服务类的实例子这样其它上下问的对象就不需要保留对象的直接引用。如果代码在一个保留基于 CLR 组件服务类引用在静态属性的的上下文执行。它实际保留的是一个引用。如果另一个上下文里的代码要通过静态属性访问它, 特定的代理就发现变化而且封装它自己保留的引用这样侦听就触发了。这个是有个优美的解决方案,本质上你可以任何地方保存基于 CLR 服务类的实例并且都会正确触发。
     
管理珍贵的资源
      我现在开始另外一个管理对象引用的话题 ' ,另外一个问题你必须注意 CLR 靠垃圾收集器回收内存。 这是个重要的事情因为它要自动帮助避免内存泄露并且释放闲置的内存。非常不幸的是, 垃圾收集器使得内存管理变的容易,但是他同样带来了其他问题,比如管理 COM+ 设计的对象变的困难。因此高效地管理资源是建立可伸缩的系统的关键,这正是 COM+ 的设计目标,你需要知道如果合理地释放基于 CLR 的资源。
     
考虑 CLR 类使用到了 SqlConnection 对象连接数据库, 如下所示 :
public void UseADatabase(void)
{
 System.Data.SqlClient.SqlConnection =
      new System.Data.SqlClient.SqlConnection(…);
    …// use connection to execute statements
}
UseADatabase 方法的结束部分, SqlConnection 对象被释放。 SqlConnection 对象封装了处理数据库的连接并且为了效率考虑被 ADO.NET 底层机制池化 (Beta 1 使用 COM+ 对象池 ; Beta 2 使用了类似 COM+ 对象池的机制 ) 问题是 SqlConnection 对象管理的连接何时归还到对象池 ? 不等垃圾收集器进入,就意识到 SqlConnection 对象不在被使用,就调用终结器。 finalizer 把连接归还给对象池, 然后 SqlConnection 对象的内存就被回收。
      
问题是数据库连接资源是非常珍贵的资源,你不能让他们无约束地飘荡在内存里,直到垃圾收集器来回收。 需要一些方法来回收这样的资源。一种方式就是强制垃圾收集器执行回收工作,如下所示 :
public void UseADatabase(void)
{
 System.Data.SqlClient.SqlConnection conn =
      new System.Data.SqlClient 。SqlConnection(…);
 … // use connection to execute statements
 conn = null;
 System.GC.Collect();   
}
然而,这个方法相当的笨拙且有很多潜在问题。
      更好的方法是调用 SqlConnection 对象实现的接口的 IDisposable.Dispose 方法。 IDisposable 接口在 System 命名空间,它规范拥有昂贵的要清理的资源的对象通过客户端定义操作释放资源代理垃圾收集器回收的行为。 SqlConnection 实现了 Dispose ,快速归还对象给对象池 ( 事实上和调用 finalizer 的作用一样 )
      
下面代码是新版本的 UseADatabase 方法,它显示的清除了 SqlConnection
public void UseADatabase(void)
{
 System.Data.SqlClient.SqlConnection conn =
      new System.Data.SqlClient.SqlConnection(…);
 try
 {
    … // use connection to execute statements
 }
 finally
 {
    Conn.Dispose();
 }
}
注意到 Dispose 出现在 finally 块里确保它可以执行,不管是否抛出异常。
      
那这个和 COM+ 与服务类有什么关系呢 ? 首先,你的服务类很可能使用数据库连接和其它昂贵的资源, 所以如果你希望系统净化,你就应该知道如果合理地释放资源。 第二是, 所有基于 CLR 服务类 实现了 IDisposable.Dispose 方法, 因为基类, ServicedComponent 。你应该知道当客户端调用 Dispose 时发生了什么。默认实现的行为取决于你的组件服务类 使用了什么运行时服务。如果你没使用对象池激活, Dispose 就会立即调用 finalizer ( 如果已经被垃圾收集器回收,就不会再调用 ) 。如果你的组件服务类 使用了对象池而不是 JIT 激活, Dispose 调用 Deactivate 去通知对象已经离开它当前的上下文。然后它就调用 CanBePooled 去询问对象是否重用或销毁。 如果 CanBePooled 返回 true 对象会返回到对象池里。 返回 false 对象的 finalizer 就会调用 ( 如果已经被垃圾收集器回收,就不会再调用 ) 非常重要是客户端为池化的对象调用方法 Dispose ,如下所示。
public void UseAPooledObject(void)
{
 MyPooledCfgClass pcc = new MyPooledCfgClass();
 try
 {
    …// use pooled object to do something
 }
 finally
 {
    Pcc.Dispose();
 }
}
       如果不调用 Dispose ,对象 ( 如描述的数据库连接 ) 只有等到垃圾收集器回收资源。 最后, 如果你自己实现组件服务类的 Dispose 方法, 你必须调用基类的实现去实现这些描述的行为。
[ Synchronization(SynchronizedOption.Required) ]
public class MyCfgClass : ServicedComponent
{
 public new void Dispose()
 {
    … // do whatever
    Base.Dispose(); // call base to clean up
 }
}
     你或许想知道 JIT 激活和反激活一个对象在方法的结束。 那解决了资源的管理问题了吗 ? 答案是否。 反激活使得对象在结束调用前释放自己成员所有的资源, 同样包括想使用相同资源的的每个方法调用。你可以通过不保存任何资源的方式达到这个目的。如果你没有选择 JIT 激活 和反激活在每次方法调用的结尾, 知道客户端调用 Dispose 实际上是强制你的对象重激活到饭激活并且重新终结一次。通常情况下, CLR 的转换不会改变的 JIT 激活规则。 应该避免使用它除非运行时服务需要它 ( 比如声明的服务 ) 因为它不会更高效, 比你自己管理昂贵的资源效率低。
展望
      ServicedComponent 类继承自 System ContextBoundObject 它又继承自 System MarshalByRefObject 这个基类可以使对象可以通过 .NET remoting 机制 被访问到。在 Remoting 顶层 上进行 CLR/COM+ 集成有两个好处。首先你能通过 Remoting 层使用 SOAP 访问 COM+ 运行时服务的远程对象。但是你不能够使用 SOAP 传递事务到另外的进程 ( 非你所愿, 但道理如此 ) 第二点,打开了新版的新的基于 CLR Remoting 上下文架构实现的 COM+ 运行时服务的方式 这是现在的 COM+ 所没有用到的。 将来一定会够发生。 真要这样,服务就可以与 SOAP 或者其他的传输无逢集成,并且上下文就可以真的可以扩展了。那时, 已经正确跨出一大步的 CLR 就会使得 COM+ 编程变的更加容易。
 
相关文章 :
Windows XP: Make Your Components More Robust with COM+ 1.5 Innovations
House of COM: Migrating Native Code to the.NET CLR
the "samples"technologies"component 服务 subdirectory of the.NET Framework SDK
事务 al COM+: Building 可伸缩的应用系统 by Tim Ewald (Addison-Wesley, 2001)
作者 Tim Ewald : DevelopMento 的首席科学家, 最近出版的 事务性 COM+: 创建可伸缩的应用系统 (Addison-Wesley 2001) 的作者
翻译 Frank Xu Lei : 程序员,技术博客 http://www.cnblogs.com/frank_xl/
来自 10 2001 MSDN 杂志