後設資料概述:後設資料是一種二進位制資訊,用以對儲存在公共語言執行庫可移植可執行檔案 (PE) 檔案或儲存在記憶體中的程式進行描述。將您的程式碼編譯為 PE 檔案時,便會將後設資料插入到該檔案的一部分中,而將程式碼轉換為 Microsoft 中間語言 (MSIL) 並將其插入到該檔案的另一部分中。在模組或程式集中定義和引用的每個型別和成員都將在後設資料中進行說明。當執行程式碼時,執行庫將後設資料載入到記憶體中,並引用它來發現有關程式碼的類、成員、繼承等資訊。
後設資料以非特定語言的方式描述在程式碼中定義的每一型別和成員。後設資料儲存以下資訊:
-
程式集的說明。
-
標識(名稱、版本、區域性、公鑰)。
-
匯出的型別。
-
該程式集所依賴的其他程式集。
-
執行所需的安全許可權。
-
-
型別的說明。
-
名稱、可見性、基類和實現的介面。
-
成員(方法、欄位、屬性、事件、巢狀的型別)。
-
-
屬性。
-
修飾型別和成員的其他說明性元素。
-
後設資料的優點
對於一種更簡單的程式設計模型來說,後設資料是關鍵,該模型不再需要介面定義語言 (IDL) 檔案、標頭檔案或任何外部元件引用方法。後設資料允許 .NET 語言自動以非特定語言的方式對其自身進行描述,而這是開發人員和使用者都無法看見的。另外,通過使用屬性,可以對後設資料進行擴充套件。後設資料具有以下主要優點:
-
自描述檔案
公共語言執行庫模組和程式集是自描述的。模組的後設資料包含與另一個模組進行互動所需的全部資訊。後設資料自動提供 COM 中 IDL 的功能,允許將一個檔案同時用於定義和實現。執行庫模組和程式集甚至不需要向作業系統註冊。結果,執行庫使用的說明始終反映編譯檔案中的實際程式碼,從而提高應用程式的可靠性。
-
語言互用性和更簡單的基於元件的設計
後設資料提供所有必需的有關已編譯程式碼的資訊,以供您從用不同語言編寫的 PE 檔案中繼承類。您可以建立用任何託管語言(任何面向公共語言執行庫的語言)編寫的任何類的例項,而不用擔心顯式封送處理或使用自定義的互用程式碼。
-
屬性
.NET Framework 允許您在編譯檔案中宣告特定種類的後設資料(稱為屬性)。在整個 .NET Framework 中到處都可以發現屬性的存在,屬性用於更精確地控制執行時您的程式如何工作。另外,您可以通過使用者定義的自定義屬性向 .NET Framework 檔案發出您自己的自定義後設資料。有關更多資訊,請參見利用屬性擴充套件。
後設資料和PE檔案結構:
後設資料儲存在 .NET Framework 可移植可執行檔案 (PE) 檔案的一個部分中,而 Microsoft 中間語言 (MSIL) 則儲存在 PE 檔案的另一部分中。檔案的後設資料部分包含一系列的表和堆資料結構。MSIL 部分包含 MSIL 和引用 PE 檔案後設資料部分的後設資料標記。當使用工具(例如,使用 MSIL 反彙編程式 (Ildasm.exe) 來檢視程式碼的 MSIL 或使用執行庫偵錯程式 (Cordbg.exe) 來執行記憶體轉儲)時,您可能會遇到後設資料標記。
後設資料表和堆
每個後設資料表都保留有關程式元素的資訊。例如,一個後設資料表說明程式碼中的類,另一個後設資料表說明欄位等。如果您的程式碼中有 10 個類,類表將有 10 行,每行一類。後設資料表引用其他的表和堆。例如,類的後設資料表引用方法表。
後設資料還以四種堆結構儲存資訊:字串、Blob、使用者字串和 GUID。所有用於對型別和成員進行命名的字串都儲存在字串堆中。例如,方法表不直接儲存特定方法的名稱,而是指向儲存在字串堆中的方法的名稱。
後設資料標記
後設資料標記在 PE 檔案的 MSIL 部分中唯一確定每個後設資料表的每一行。後設資料標記在概念上和指標相似,永久駐留在 MSIL 中,引用特定的後設資料表。
後設資料標記是一個四個位元組的數字。最高位位元組表示特定標記(方法、型別等)引用的後設資料表。剩下的三個位元組指定與所說明的程式設計元素對應的後設資料表中的行。如果您用 C# 定義一個方法並將其編譯到 PE 檔案,下面的後設資料標記可能存在於 PE 檔案的 MSIL 部分:
0x06000004 |
其中最高位位元組 (0x06) 表示這是一個 MethodDef 標記。低位的三個位元組 (000004) 指示公共語言執行庫在MethodDef 表的第四行查詢對該方法定義進行描述的資訊。
PE 檔案中的後設資料
當為公共語言執行庫編譯程式時,該程式轉換為由三部分組成的 PE 檔案。下表說明了每部分的內容。
PE部分 |
PE 部分的內容 |
---|---|
PE 標頭 |
PE 檔案主要部分的索引和入口點的地址。 執行庫使用該資訊確定該檔案為 PE 檔案並確定當將程式載入到記憶體時執行從何處開始。 |
MSIL 指令 |
組成程式碼的 Microsoft 中間語言指令 (MSIL)。許多 MSIL 指令帶有後設資料標記。 |
後設資料 |
後設資料表和堆。執行庫使用該部分記錄您的程式碼中每個型別和成員的資訊。本部分還包括自定義屬性和安全性資訊。 |
後設資料在執行時的作用:
要更好地理解後設資料和它在公共語言執行庫中的作用,構造一個簡單的程式並說明後設資料如何影響它的執行時情況可能很有幫助。下面的程式碼示例顯示名為 MyApp 的類中的兩種方法。Main 方法是程式入口點,而 Add 方法只返回兩個整數引數的和。
- using System;
- public class MyApp
- {
- public static int Main()
- {
- int ValueOne = 10;
- int ValueTwo = 20;
- Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
- return 0;
- }
- public static int Add(int One, int Two)
- {
- return (One + Two);
- }
- }
當程式碼執行時,執行庫將模組載入到記憶體並向後設資料諮詢該類的資訊。載入後,執行庫對方法的 Microsoft 中間語言 (MSIL) 流執行廣泛的分析,將其轉換為快速本機指令。執行庫根據需要使用實時 (JIT) 編譯器將 MSIL 指令轉換為本機程式碼,每次轉換一個方法。
下面的示例顯示了從以前程式碼的 Main 功能生成的部分 MSIL。您可以使用 MSIL 反彙編程式 (Ildasm.exe) 從任何 .NET Framework 應用程式中檢視 MSIL 和後設資料。
- .entrypoint
- .maxstack 3
- .locals ([0] int32 ValueOne,
- [1] int32 ValueTwo,
- [2] int32 V_2,
- [3] int32 V_3)
- IL_0000: ldc.i4.s 10
- IL_0002: stloc.0
- IL_0003: ldc.i4.s 20
- IL_0005: stloc.1
- IL_0006: ldstr "The Value is: {0}"
- IL_000b: ldloc.0
- IL_000c: ldloc.1
- IL_000d: call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */
JIT 編譯器讀取整個方法的 MSIL,對其進行徹底地分析,然後為該方法生成有效的本機指令。在 IL_000d 遇到Add 方法 (/*06000003 */) 的後設資料標記,執行庫使用該標記參考 MethodDef 表的第三行。
下表顯示了說明 Add 方法的後設資料標記所引用的 MethodDef 表的一部分。雖然程式集中存在其他後設資料表並具有它們自己唯一的值,但這裡只討論該表。
Row |
相對虛擬地址 (RVA) |
ImplFlags |
Flags |
Name (指向字串堆。) |
Signature(指向 Blob 堆) |
---|---|---|---|---|---|
1 |
0x00002050 |
IL Managed |
Public ReuseSlot SpecialName RTSpecialName .ctor |
.ctor(建構函式) |
|
2 |
0x00002058 |
IL Managed |
Public Static ReuseSlot |
Main |
String |
3 |
0x0000208c |
IL Managed |
Public Static ReuseSlot |
Add |
int, int, int |
該表的每一列都包含有關程式碼的重要資訊。RVA 列允許執行庫計算定義該方法的 MSIL 的起始記憶體地址。ImplFlags 和 Flags 列包含說明該方法的位遮蔽(例如,該方法是公共的還是私有的)。Name 列對來自字串堆的方法的名稱進行了索引。Signature 列對在 Blob 堆中的方法簽名的定義進行了索引。
執行庫在第三行的 RVA 列計算所需的偏移量地址並將該地址返回到 JIT 編譯器,然後,JIT 編譯器進入新地址。JIT 編譯器繼續在新地址處理 MSIL,直到它遇到另一個後設資料標記,之後,重複該過程。
使用後設資料,執行庫可以訪問載入程式碼並將其處理為本機指令所需的所有資訊。以這種方式,後設資料使自描述檔案、公共型別系統和跨語言繼承成為可能。