.NET 4.0: Type Equivalency (2) - 內嵌類型

上次我們談到了.NET 4.0中爲什麼要引入Type Equivalency這樣一個新功能,這次我們來看一個比較簡單的一個程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;
 
namespace NOPIAExcelDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Application excelApp = new Application();
            excelApp.Workbooks.Add();
            excelApp.Visible = true;
        }
    }
}

這個程序調用Excel,創建一個新的Workbook,並把Excel主程序設置爲可見。

首先,我們在VS 2010中添加一個新的C#控制檯項目,然後在Solution Explorer中選擇Add Reference,選擇Excel 12的Interop Assembly:

image

選擇點擊OK之後,在Reference下面會多出一項Microsoft.Office.Interop.Excel的引用,在其上右鍵點擊選擇Properties:

image

裏面有一項Embed Interop Types,修改爲True。

完成之後運行程序,沒發現區別是不是?呵呵,這就對了。我們回頭來看看生成的代碼是什麼樣子的。

用ILDASM打開生成的EXE,雙擊Manifest,結果如下:

// Metadata version: v4.0.11001
.assembly extern mscorlib
{
   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z/V.4..
   .ver 4:0:0:0
}
.assembly extern System.Core
{
   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z/V.4..
   .ver 4:0:0:0
}
.assembly NOPIAExcelDemo
{
    // ...
}

上面的結果說明什麼呢?雖然我們之前通過Add Reference讓這個項目引用了Excel的PIA,但是,這個EXE並沒有對Excel的PIA的引用,也就是說,這個EXE可以獨立於PIA運行!這也正是這個Feature的最直接的作用:消除PIA,直接將Interop相關的託管類型直接嵌入(Embed)到EXE中。現在我們再看看這個EXE中又有那些類型:

image

非常清楚,除了NOPIAExcelDemo本身的類型之外,這個EXE把Excel的PIA中部分類型,如_Application, _Workbook, WorkBooks…等等,都包括進來了。注意這些類型都是剛纔的程序所引用到的,而沒有用到是不會出現在這個EXE中的。再進一步看看Workbooks這個接口。注意我們調用到了了Workbooks.Add方法,而在這個EXE中的Workbooks類型也只有Add方法!那麼其他方法都去了那裏呢?讓我們再回頭看看PIA中的Workbooks類型是什麼樣子的:

image

可以看到其中的方法要多上不少。而且EXE中的Workbooks類型還有兩個奇怪的_VtblGap1_3,_VtblGap2_15。這兩個函數的作用是什麼呢?CLR的Interop有一個不太爲人知的特性:因爲COM中的接口是基於虛函數表的,如果只調用虛函數表中的某個函數,而不用到其他函數,那麼其他函數的入口點是可以不需要的,只需要用到的那個函數在正確的虛表位置,指向正確的函數地址即可。因此,CLR提供了一個功能,凡是接口中以_VtblGap_<N>_<M>形式命名的函數,都視爲虛函數表中的M個空白項(注意N值忽略)。以Workbooks函數爲例,第一個_VtblGap1_3函數表明這裏有三個不用到的虛函數表項,對應着Workbooks裏面的get_Application, get_Creator, get_Parent函數。Get_Parent之後正好是Add函數。在Add函數之後,又有一個_VtblGap2_15,對應着從Close到OpenXML這15個函數。注意.NET屬性並非函數(而是由編譯器翻譯成對應的函數),因此不算在內。這種做法可以有效的節約空間佔用,減少用到的接口的複雜性。這個將Interop Assembly中的類型“拖入”到用戶的EXE中的操作,我們內部稱之爲Pull-in,是由C#編譯器實現的。

談了這麼多,總結一下:C#編譯器允許將Interop Assembly中的類型直接嵌入在最終生成的EXE中,從而斷絕和Interop Assembly(包括PIA)之間的引用關係。需要Interop Assembly從此只限於在編譯時,而非運行時。

不過,這個只解決了問題的一部分,那就是如何避免最終的EXE和Interop Assembly之間存在引用關係。然而,最重要的是,如果在另外的Assembly中,也引用到了另外版本的Interop Assembly中的某個類型,比如Office 11中的Workbooks,在這種情況下,如何解決託管類型衝突的問題呢?答案在TypeIdentifierAttribute中。如果我們查看EXE中的Workbooks的接口定義,我們會發現下面的內容:

.class interface public abstract auto ansi import Microsoft.Office.Interop.Excel.Workbooks
       implements [mscorlib]System.Collections.IEnumerable
{
  .custom instance void [mscorlib]System.Runtime.InteropServices.TypeIdentifierAttribute::.ctor(string,
                                                                                                string) = ( 01 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 30 30 30 32 30 38 44 42 2D 30 30 30 30   // ..$000208DB-0000
                                                                                                  2D 30 30 30 30 2D 43 30 30 30 2D 30 30 30 30 30   // -0000-C000-00000
                                                                                                  30 30 30 30 30 34 36 00 00 )                      // 0000046..
} // end of class Microsoft.Office.Interop.Excel.Workbooks

其中的TypeIdentifierAttribute是Type Equivalency,也就是NOPIA這個Feature的核心內容。在下一篇文章中,我將講解這個Attribute的作用,以及C#編譯器如何處理具有這個Attribute的類型之間的交互。


作者
: 張羿              
轉載請註明出處

發佈了119 篇原創文章 · 獲贊 3 · 訪問量 78萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章