第6章阐述了用于调用方法的各种实用部件。然而,一旦方法被调用,控制权就被传递给目标对象的方法,调用过程就结束了。本章主要讨论由CLR提供的用于在调用方和被调用方之间截获调用过程的各个实用部件。
尤其需要注意的是:本章描述的一部分方法凋用架构,很可能会在将来的CLR版本中得以显著地发展。换而言之,本章提到的许多概念和技术到将来仍然有效。本章论述的侧重点在于对完整性的考虑,而不仅仅是作为给读者的介绍。
动机
软件工程的大多数思想重点在于管理复杂性。结构化编程试图通过对代码和设计进行粗糙地分离,以减少复杂性:面向对象编程试图通过构建抽象事物,将状态和行为组合在一起,以减少复杂性:组件通过基于抽象接口或者协议分离应用程序,以减少复杂性。组件的意图就是为了让技能不高的程序员,使用高级语言或者工具对组件进行装配。当然,这是基于一种假设前提,即问题域(problem domain)可以被分解成离散的组件,它们只是通过简单的方法调用相互作用,
组件的基本前提忽略了—个事实,那就是程序的某些方面(aspect)可能渗透到应用程序的所有部分。安全性就是这样的一个方面。此外,还有线程管理、并发性控制等等。
应用程序中通常会掺杂一些代码片段(tiny snippets),来处理程序的那些方面,而它们并不是问题域的中心。这样的方面往往是跨问题域的,并且要求有可重用的解决方案。对于这类问题提供可重用的机制是面向方面蝙程(aspect-oriented programming,AOP)的重点。这里,AOP是一个由XefOXPARC研究中心的Gregor Kiczales在1997年创建的术语。
AOP的重点在于提供一种机制将应用程序分解成多个块.这些块是建立在concern分离的原则上的,与问题域关系不大。这样做有两个好处.第一,应用程序的代码不再被那些“管道(plumbing)代码掺合,而后者与待解决的问题并没有什么关系:第二,通过将跨问题域的代码的这些方面分解出来,你可以在其他应用程序中重用这些解决方案。
微软事务服务器( MicrosofC Transaction Server,MTS)应该是最先广泛采用AOP的应用程序。MTS提供了一种特性机制,可以用来表示程序的不同方面,例如,常规代码流之外的事务性和安全性。MTS通过截获(interception)实现这些方面。MTS自身将插入到调用方和组件之间,并预处理(preprocess)或后处理(postprocess)组件中的各个方法调用。在预处理和后处理期间,MTS执行程序(MTS executive)将管理事务、处理安全检查,以及激活对象。对于组件开发人员而言,所有这些都不需要显式地编码。尽管事务极可能影响组什的整个设计,但是并不需要显式地编码来管理事务。更进一步地说,MTS执行程序将在后台静静地处理所有这些问题。
在COM时代构建MTS风格的截获管道(interception plumbing)是相当困难的。原因之一就是缺乏高保真的(high-fidelity)可扩展元数据.显而易见,在基于CLR的世界中,这不再是问题。COM时代的另一个难题是:要透明地插入代码,且不能弄乱了堆栈,就要处理IA-32调用堆栈的特别性(peculiarity)。为了避免堆栈的混合(melt),总是需要求助于IA-32汇编语言。CLR架构的主要目标之一,就是提供一个通用的截获机制,使我们不用求助于讨厌的底层编码技术,而插入用户自定义的这些方面。CLR的截获机制的最基本概念就是调用能被当作消息进行交换。
作为方法调用的消息
CLR提供了一个丰富的架构,用于将方法调用模型化,使它可以作为消息进行交换。选种架构对于构建AOP风格的截获、RPC风格的通信机制,以及处理异步调用都是很有用的,实际上,在第6章描述的异步方法调用功能就使用这种架构,理解这种架构的关键点就是重新考察方法实际上做了什么。归根结底,方法只是在堆栈上进行简单地内存转换。顺便说一句,功能化编程的拥护者(functional programming advocates)认为这就是方法的全部。调用方通过以方法指定的格式将适台的参数压入堆栈,从而形成一个调用堆栈。在控制权被传递给方法之后,这个方法的主要工作便是处理传递给堆栈的参数,并且重写堆栈以表明处理的结果。图7.l描述了这个过程。
CLR允许你以消息交换的形式,对这种堆栈帧(stack frame)的转换进行模型化。在这种消息交换的模型中,一个方法调用有两个消息:一个表示调用请求,另一个表示调用结果。为了按可编程的方式进行访问,CLR将这些消息模型化,即将它们作为实现了System.Runtime.Remoting.Messiging.IMessage接口的对象。如图7.2所示,IMessage充当在套件(suite)中其他接口的基接口。这些接口中最令人感兴趣的是IMethodMessage。
using System.Reflecton;
using System.Collectons;
namespace System.Runtime.Remoting.Messageing{
public
interface IMessage{
IDictionary Properties{get;}
}
public interface IMethodMessage:IMessage{
object GetArg(intindex);
string GetArgName(int index);
int ArgCount{get;}
object[] Args{get;}
bool HasVarArgs{get;}
LogicalCallContext LogicalCallContext{get;}
MethodBase MethodBase{get;}
string Methodname{get;}
object MethodSignature{get;}
string TypeName{get;}
string Uri{get;}
}
}
注意,除了提供对方法参数的访问之外,IMethodMessage接口还提供 了MethodBase属性,用于访问方法的元数据。通过这个通用接口公开堆栈帧的好处就足:它允许你访问堆栈的内容,而不需要知道底层的堆栈帧布局。例如,考虑下面的代码:
static void WireTap(IMethodMessage msg)
{
IMethodCallMessage call=(IMethodCallMessage)msg;
Console.WriteLine("<{0}>",call.MethodName);
for(int i=0;i<call.ArgCount;++i)
{
Console.WriteLine("<{0}>{0}</{0}>",call.GEtArgName(i),call.GEtArg(i),Call.GEtArgName(i));
}
Console.WriteLine("</{0}>",call.MethodName);
}
假定这个例程被表示的消息对应于下面的调用:
int b=2;
int c=3;
int n=foo.MyMetthod(1,ref b,out c);
这将显示下面的结果:
<MyMethod>
<arg1>1</arg1>
<arg2>2</arg2>
<arg3>3</arg3>
</MyMethod>
这种机制的强大之处在于:Wiretap方法不需要事先知道MethodName的签名。这是因为IMethodMessage接口将调用堆栈虚拟化成一个合理的可编程的抽象体。