.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,並解決託管類型不一致這樣一個問題。


--
作者: 張羿(ATField)
Blog: http://blog.csdn.net/atfield
http://blogs.msdn.com/yizhang
轉載請註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章