元数据在运行时的作用
要更好地理解元数据和它在公共语言运行时中的作用,构造一个简单的程序并说明元数据如何影响它的运行时情况可能很有帮助。 下面的代码示例显示名为 MyApp 的类中的两种方法。 Main 方法是程序入口点,而 Add 方法只返回两个整数参数的和。
Public Class MyApp
Public Shared Sub Main()
Dim ValueOne As Integer = 10
Dim ValueTwo As Integer = 20
Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo))
End Sub
Public Shared Function Add(One As Integer, Two As Integer) As Integer
Return (One + Two)
End Function
End Class
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 表的一部分。 虽然程序集中存在其他元数据表并具有它们自己唯一的值,但这里只讨论该表。
行 |
相对虚拟地址 (RVA) |
ImplFlags |
Flags |
名称 (指向字符串堆。) |
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 |
添加 |
int, int, int |
该表的每一列都包含有关代码的重要信息。 RVA 列允许运行时计算定义该方法的 MSIL 的起始内存地址。 ImplFlags 和 Flags 列包含说明该方法的位屏蔽(例如,该方法是公共的还是私有的)。 Name 列对来自字符串堆的方法的名称进行了索引。 Signature 列对在 Blob 堆中的方法签名的定义进行了索引。
运行时在第三行的 RVA 列计算所需的偏移量地址并将该地址返回到 JIT 编译器,然后,JIT 编译器进入新地址。 JIT 编译器继续在新地址处理 MSIL,直到它遇到另一个元数据标记,之后,重复该过程。
使用元数据,运行时可以访问加载代码并将其处理为本机指令所需的所有信息。 以这种方式,元数据使自描述文件、常规类型系统和跨语言继承成为可能。