<p style="text-indent: 21pt;"><font color="#408080">虽然遭遇到很多人的批评,但还是最终坚持下来。在这个系列中,主要是针对平时使用VCL的过程中,感觉Delphi没有处理好的地方。偶尔能提出一些自己的看法,权当谈资。更重要地是,我们可以借此学习到一些思想。</font></p>
<p style="text-indent: 21pt;">Delphi比起其他语言,有一个非常明显的特点,那就是类引用。典型的声明就是:</p>
<p style="text-indent: 21pt;">TComponentClass = class of TComponent;</p>
<p style="text-indent: 21pt;">可不要小看这个简单的声明,他可以让你直接访问到类的VMT。在李维先生的《Inside VCL》中谈到Delphi.NET的时候,也对此有特别的说明。对象和对象的差异,从一定意义上讲,就在于其所指向的VMT的不一样。</p>
<p style="text-indent: 21pt;">在VCL中,每个类的VMT都有自己的地址空间。于是VCL在判断两个类类型是否一致的时候,就是简单使用地址相等的方式。这里面最常用的就是Is操作符的实现了。遍历所有父类型,判断是否一致。</p>
<p style="text-indent: 21pt;">与VMT类似的,Delphi中的一些基本类型也是有自己的类型地址空间。这些类型的保存如果在C#中,你要说是类型反射。但在Delphi中,你更可以直接说成是为了实现published属性的类型检查。他们的类型检查也同样是使用地址空间来判断的。比如,判断一个属性是否为Boolean。你可以在TypInfo单元中找到相对应的实现代码。</p>
<p style="text-indent: 21pt;">我在实现一个TObjectGrid的时候,使用了RTTI信息来获取对象中属性的值。有一个需求是,判断某一个格子Cell所对应的属性是否为Boolean,如果是的话,使用CheckBox模式进行编辑。最初的实现代码是这样的:</p>
<p style="text-indent: 21pt;">if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) then ...</p>
<p style="text-indent: 21pt;">这段代码一直执行得很正常,直到有一天,我们的业务数据对象是从Dll返回。这个判断一下子实效了!</p>
<p style="text-indent: 21pt;">能够理解这个问题的人,应该能看出来,这是由于这个类型判断是使用地址空间引起的。Exe和Dll里有着自己独立的类型地址空间。当Dll加载到Exe中时,Dll中的类型地址空间经过重定位,因此Dll中的Boolean类型地址,和Exe中的类型地址不在时一个地址空间。我后来不得以,使用了名称的方式来判断类型是否一致。</p>
<p style="text-indent: 21pt;">如果你在Dll的接口中定义了TStrings类型的属性,那么在Exe中使用了TStrings类的Assign功能时会发生以外。这是因为TStrings实现Assign功能的时候,有这样一个判断:</p>
<p style="text-indent: 21pt;">if Source is TStrings then</p>
<p style="text-indent: 21pt;">经过上面的讲解,这段代码的判断显然返回False,那么其下面的赋值操作必然不会被执行。当然了,说到Is,应该能够想到,As操作符也会有类似的表现。因为As调用Is判断啊。</p>
<p style="text-indent: 21pt;">我上面讲了半天,其实就是说Exe和Dll之间的同一个类型不能够完全等同看待。特别是在接口之间传递的时候。当然了,我们可以说约定好,类型传递,不允许使用非基本类型。但是传递基础类的诱惑确实不小,这样,就少了很多处理了。</p>
<p style="text-indent: 21pt;">不过,正如LSuper大侠曾经提到的,在Delphi的Bpl之中就没有这个问题,因为他们的类型都是共享的。</p>
<p style="text-indent: 21pt;">应该说,针对VCL中的这个弱点,好的设计并不容易寻找。.NET中的Dll,已经实现了类型共享,这个Delphi的Bpl思想一致。想起来,应该是Dll的规范早于面向对象语言流行的原因。在早先的Dll定义中,并没有考虑复杂类型的接口传递。因此Delphi在上面实现会遇到很多问题。最大的就是语言和版本的问题。</p>
<ol>
<li>
<div style="text-indent: 21pt;">不同的语言实现的Dll,如何保障类型相通?</div>
</li>
<li>
<div style="text-indent: 21pt;">不同版本的VCL类型,如何做到一致?</div>
</li>
</ol>
<p style="text-indent: 21pt;">解决了这两个问题,类型就可以自由穿过应用程序边界了。我并不是希望Delphi重新设计这个缺陷。不过,可惜的是,Delphi并没有将自己的Bpl推广开来。就如.NET修改了Dll的规范一样。如果Delphi的Bpl也直接变成Dll,呵呵,还不知道结果会是什么样呢。</p>
<p style="text-indent: 21pt;">Delphi比起其他语言,有一个非常明显的特点,那就是类引用。典型的声明就是:</p>
<p style="text-indent: 21pt;">TComponentClass = class of TComponent;</p>
<p style="text-indent: 21pt;">可不要小看这个简单的声明,他可以让你直接访问到类的VMT。在李维先生的《Inside VCL》中谈到Delphi.NET的时候,也对此有特别的说明。对象和对象的差异,从一定意义上讲,就在于其所指向的VMT的不一样。</p>
<p style="text-indent: 21pt;">在VCL中,每个类的VMT都有自己的地址空间。于是VCL在判断两个类类型是否一致的时候,就是简单使用地址相等的方式。这里面最常用的就是Is操作符的实现了。遍历所有父类型,判断是否一致。</p>
<p style="text-indent: 21pt;">与VMT类似的,Delphi中的一些基本类型也是有自己的类型地址空间。这些类型的保存如果在C#中,你要说是类型反射。但在Delphi中,你更可以直接说成是为了实现published属性的类型检查。他们的类型检查也同样是使用地址空间来判断的。比如,判断一个属性是否为Boolean。你可以在TypInfo单元中找到相对应的实现代码。</p>
<p style="text-indent: 21pt;">我在实现一个TObjectGrid的时候,使用了RTTI信息来获取对象中属性的值。有一个需求是,判断某一个格子Cell所对应的属性是否为Boolean,如果是的话,使用CheckBox模式进行编辑。最初的实现代码是这样的:</p>
<p style="text-indent: 21pt;">if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) then ...</p>
<p style="text-indent: 21pt;">这段代码一直执行得很正常,直到有一天,我们的业务数据对象是从Dll返回。这个判断一下子实效了!</p>
<p style="text-indent: 21pt;">能够理解这个问题的人,应该能看出来,这是由于这个类型判断是使用地址空间引起的。Exe和Dll里有着自己独立的类型地址空间。当Dll加载到Exe中时,Dll中的类型地址空间经过重定位,因此Dll中的Boolean类型地址,和Exe中的类型地址不在时一个地址空间。我后来不得以,使用了名称的方式来判断类型是否一致。</p>
<p style="text-indent: 21pt;">如果你在Dll的接口中定义了TStrings类型的属性,那么在Exe中使用了TStrings类的Assign功能时会发生以外。这是因为TStrings实现Assign功能的时候,有这样一个判断:</p>
<p style="text-indent: 21pt;">if Source is TStrings then</p>
<p style="text-indent: 21pt;">经过上面的讲解,这段代码的判断显然返回False,那么其下面的赋值操作必然不会被执行。当然了,说到Is,应该能够想到,As操作符也会有类似的表现。因为As调用Is判断啊。</p>
<p style="text-indent: 21pt;">我上面讲了半天,其实就是说Exe和Dll之间的同一个类型不能够完全等同看待。特别是在接口之间传递的时候。当然了,我们可以说约定好,类型传递,不允许使用非基本类型。但是传递基础类的诱惑确实不小,这样,就少了很多处理了。</p>
<p style="text-indent: 21pt;">不过,正如LSuper大侠曾经提到的,在Delphi的Bpl之中就没有这个问题,因为他们的类型都是共享的。</p>
<p style="text-indent: 21pt;">应该说,针对VCL中的这个弱点,好的设计并不容易寻找。.NET中的Dll,已经实现了类型共享,这个Delphi的Bpl思想一致。想起来,应该是Dll的规范早于面向对象语言流行的原因。在早先的Dll定义中,并没有考虑复杂类型的接口传递。因此Delphi在上面实现会遇到很多问题。最大的就是语言和版本的问题。</p>
<ol>
<li>
<div style="text-indent: 21pt;">不同的语言实现的Dll,如何保障类型相通?</div>
</li>
<li>
<div style="text-indent: 21pt;">不同版本的VCL类型,如何做到一致?</div>
</li>
</ol>
<p style="text-indent: 21pt;">解决了这两个问题,类型就可以自由穿过应用程序边界了。我并不是希望Delphi重新设计这个缺陷。不过,可惜的是,Delphi并没有将自己的Bpl推广开来。就如.NET修改了Dll的规范一样。如果Delphi的Bpl也直接变成Dll,呵呵,还不知道结果会是什么样呢。</p>