.NET 4.0: Type Equivalency (1) – Byebye,PIA

在.NET 4.0 CTP中,最引人注目的Interop的改进当属Type Equivalency,又称之为NO PIA。在介绍如何使用这个新特性之前,我觉得还是应该从历史讲起,探究我们究竟要解决什么样子的问题,过去采用了什么样的解决办法,以及这些办法都有那些问题。

在.NET中,为了访问COM对象,需要定义一系列托管的Interface、Struct、以及class。有了类型库(TypeLibrary)之后,使用TlbImp便可以自动生成一个Assembly,我们称之为Interop Assembly,简称为IA,里面包含有TypeLibrary所对应的类型,如TlbImp stdole2.tlb结果为:

image

然后,开发人员所写的程序可以直接引用这个DLL,使用其中定义的类型来访问对应的COM对象。在单个程序中,这个模型工作的相当好。

但是,在实际项目项目中,我们可能会遇到这样一个问题:A公司发布某个COM组件A,并同步发布对应的.NET Wrapper,并且引用到了stdole中的某个类型。该组件A的开发者选择自己用TlbImp来产生一个stdole2.tlb的对应的interop Assembly,我们称之为stdoleA。同时,另外一个COM组件B,由B公司开发,由于类似原因,也有一份他们自己的Stdole的Copy,称之为stdoleB。这两个公司之间显然互相不知道,因此无法让他们之间合作共用一个stdole的Copy,而且合作会带来各种各样的依赖方面的问题,两个公司肯定都不愿意引入对对方的依赖关系。而且,这两个组件经过Sign,也不能修改他们使之指向我们自己的Stdole.dll。重新生成自己的Wrapper也不太现实,也很不方便,因为他们的Wrapper有他们自己定义的一些很方便的Helper。

一旦使用了两个不同的Stdole.dll,对于.NET/CLR来讲,StdoleA和StdoleB是不同的DLL,虽然他们的定义完全相同,其中的类型完全相同,但是其中的类型是完全不同的类型,因为这些类型具有不同的身份(Identity)。编译器/.NET/CLR均不允许两个Stdole.dll中的类型互相互换使用而不经过类型转换,而且有些情况下,比如对于Struct而言,直接转换是不合法的。

为了解决这个问题,.NET引入了一个概念,称之为PIA。对于一个Type Library的发布者而言,可以提供一个“官方”的Interop Assembly,其他Assembly则是直接引用这个Interop Assembly而不是使用TlbImp产生属于自己的Copy,这样大家都使用一份Copy,便可以解决上面所提到的问题。这个特殊的Interop Assembly,我们称之为Primary Interop Assembly,简称PIA。PIA应该是被Sign,并且注册到GAC里面。大家平时比较常见的PIA是Office所提供的PIA。

但是,在长时间的实践中,我们发现引入PIA这个概念带来的新的问题:

1. PIA的部署比较困难

a. 对于一个Type Library而言,很多时候不太容易确定谁是最终的拥有者,谁来负责发布这个PIA。对于Office来说很简单,但是对于上面的stdole的例子,难道Windows也需要对于所有Windows的Tlb都发布一个PIA吗?那么对于XP这样的不带.NET Framework的又怎么办呢?

b. 通常,部署PIA是在安装COM组件和TypeLib的时候。比如安装Office的时候,Word/Excel等等都有对应的COM组件(严格来说这些应用程序同时都是COM服务器)和TypeLib,对应的PIA也会在安装的时候安装到GAC中。但是, 如果这台机器上不带.NET Framework,将无法安装PIA。

2. 无法自定义PIA:一旦PIA被发布,那么用户是无法修改PIA的,一旦PIA中类型的定义不符合你的要求,开发者很有可能又会定义自己的版本,那么这又意味着再次陷入了之前的两难境地。

3. PIA的大小:PIA通常非常大,特别是像Office这样的支持COM的应用程序而言,很容易到达1M左右

4. PIA的版本问题:COM组件有他的特殊性,一旦一个Interface被定义好,那么这个Interface是不能被改变的。假设你写好了一个程序可以使用IMyObject接口来访问COM组件,那么新版本的COM组件也可以通过ImyObject接口访问,同时也许可以通过ImyObject2接口来访问。老程序可以不经修改,直接使用新的COM组件。对于PIA来说,如果你写的程序引用了一个老版本的PIA,因为我们同时建议COM组件在安装的时候,发布一个Redirection Policy把所有对于老版本的引用重定位到新版本的PIA,那么这个程序仍然可以在新版本上工作。但是,如果你写的程序引用了一个新版本的PIA,那么是不可以使用在老版本的COM最近上的,即使你只用到了老接口IMyObject的功能,理论上是可以使用老版本的COM组件的。问题的原因在于PIA。

5. 绑定强类型RCW:PIA通常带有强类型的RCW,而对于COM而言,天生是为接口设计的,并不适合和绑定在一个静态的类型上。关于强类型和弱类型RCW请参看我之前写的一篇文章:什么是System.__ComObject: 强类型RCW和弱类型RCW

既然PIA有着这么多的问题,那么我们怎么解决呢?其实这个问题可以关注它的本质:最初的问题是,不同身份(Identity)的托管类型对应同一个非托管的COM类型(如接口)。那么我们换一种思路看,如果允许.NET/CLR把这两种类型真正看成一种类型呢?其实本来大部分COM的类型都是有GUID作为唯一的标识符的,那么对于对应同一个COM类型的托管类型,他们的GUID必然也是一样的,这就有了将他们认识为同一个托管类型的基础,这便是Type Equivalency功能的主要内容。因为使用了该功能将不会再用到PIA,因此这个功能内部也被称为NO PIA。下一篇文章中,我讲结合实际例子,使用.NET 4.0 + Visual Studio 2010 CTP,演示如何使用Type Equivalency功能让.NET和C#编译器通力合作,避免使用PIA,并解决托管类型不一致这样一个问题。

--
作者: 张羿
转载请注明出处

你可能感兴趣的:(.net)