关于反射的用途是『降低模块间的耦合度』这个倒未必尽然
单就delphi来说,从实现上看,它的所谓反射是基于RTTI,而RTTI的出现按照官方的说法是为了实现RAD中窗体文件DFM的持久化而产生的,其实也不是针对DFM文件或TForm啦,由于TPersistent在声明的时候加上了{$M+},所以从TPersistent派生的对象都在编译的时候添加了RTTI,而在TComponent中又增加了对TReader和TWriter支持,说的准确一点、时髦一点RTTI是为了实现对象的持久化和反持久化,在Delphi3之后加入了接口,从而将COM引入,而在delphi6以后又加入了对接口的RTTI的支持,从而将SOAP也引入到了delphi之中,从这两个重大特性的加入上看到,除了Delphi对接口支持的越来越丰富,还有一点很重要的是Delphi很早就支持了远程方法调用RPC,对于RPC的支持并不是简单的对传输协议的支持,而是要在对象定义、编译层面对其实现的支持。尽管COM的RPC与SOAP的RPC在表象上是有所不同的,但我们其实可以断定,在Delphi6之后已经就可以对对象的方法动态调用了。事实上也确实如此,一切奥秘尽包含在TypInfo,IntfInfo,ObjAuto,Invoker这些单元之中,只可惜在Help中并没有体现。我记得李维发过这样一句叹息,说其实在Borland实验室中十年前就有很多天才很多很Cool的想法但是得不到老板的支持,最后只得痛惜离开Borland。我在翻看VCL代码的时候,相信这确实是真的,现在很多在.NET或Java上很时髦的特性,有多少是Delphi在语言层面上是不支持的呢,我们忽略了Delphi是因为它们还没有来得及被Borland的工程师推向前台、还没来得及将其做成框架发布出来,而这些工程师就被赶跑了。
可能是误会了反射的意思,这里并不是指对另外单元中声明的引用,而是指在运行时可以获得代码的特性以及在运行时可以改变它的行为。这样说可能抽象一点,说的稍微形象一点,一般编译型语言中,程序中所有定义的变量和函数,它们是否被使用、如何被使用,都是要在编码的时候设定好全部的执行逻辑的,而运行时只是遵循其中一条或几条执行路径执行的,而所谓的反射就是要能够在运行时通过一个外部的控制来决定程序内部的执行行为,也就是说从逻辑上看,程序不再是一个闭包的逻辑系统,而可以依赖于外部的注入。简单的说程序的运行时结构是由许多代码段、堆和栈这三种内存块组成,而所谓的执行就是指内部逻辑可以访问到代码段中的所有定义,将函数代码放在栈上展开执行,在堆中间分配所需空间以及一些其他的诸如IO访问等行为的集合。所以,如果要实现外部的控制注入,即反射,至少需要满足能够访问类似代码段上数据而获得变量和函数定义的部分,其次还要能够控制函数在栈上展开执行。
Delphi这种基于RTTI信息而实现的反射和Java、.Net有显著的不同。由于Java、.Net是先将程序编译成bytecode或CIL,在将中间代码让VM来执行,所以在这一类语言上实现反射的基本方式是获得bytecode和CIL,这部分内容就类似于代码段上的信息,然后外部注入的逻辑被重新编译生成新的中间代码后再让VM执行,从而产生新的行为。在这种机制下,非但可以实现反射,而且还可以对中间代码实行反编译而获得原始代码。而Delphi对反射的实现和Java以及.Net有本质的不同,它将对象的字段和函数的信息编译进RTTI,在编码的时候将对象注册进一个全局的列表中,运行时通过访问该列表而获得所需的对象,再从对象的RTTI中获得字段和函数的信息,同时提供出一种可以在运行时执行对象方法的函数,也不需要再编译,于是就间接的实现了反射机制。但是我们也应该注意到Delphi的反射和Java相比,首先是并不能反射到所有的变量和函数,例如全局的函数,静态变量就不会获得,其次由于不存在中间代码,也不存在反编译的问题,当然,这些都不是重点。其实这些东西在Delphi6\7的时候就已经有了,因为要支持SOAP,在SOAP标准中有一个SOAP的RPC表示,也就是说SOAP在调用远程对象方法的时候,也就是通过字符串来驱动的,所以不但有对象的RTTI,也可以在声明接口的 时候添加{$M+}来编译进接口的RTTI,从而对接口方法也可以使用字符串来驱动。又因为最初的设计是针对SOAP的,所以在ObjAuto单元中ObjectInvoke方法不支持对象类型和指针类型,毕竟SOAP在远程调用方法时传物理地址是没有意义的,当然,Variant也是不支持对象类型的。