本文是这个系列第三篇,上一篇简单介绍了一下CLR如何调用托管程序集代码,并且提到了元数据。这篇文章将通过回答几个问题来介绍一下CLR中元数据的相关知识。
元数据
什么是元数据?
概述:
在.NET Framework中,元数据(Metadata)是描述程序代码的数据。它是由编译器生成的结构化信息,包含了关于程序的类型、成员、引用等详细信息。可以理解为程序代码的自描述信息。
元数据包括以下内容:
- 类型定义:类、结构、接口、枚举等类型的定义信息。
- 成员信息:类型的字段、方法、属性、事件等的描述。
- 引用信息:程序中引用的其他程序集和类型。
- 特性(Attributes):附加在类型或成员上的额外信息。
《CLR via C#》:
元数据是由几个表构成的二进制数据块。有三种表,分别是定义表(definition table)、引用表(reference table)和清单表(manifest table)。表2-1总结了模块元数据块中常用的定义表。
编译器编译源代码时,代码定义的任何东西都导致在表2-1列出的某个表中创建一个记录项。此外,编译器还会检测源代码引用的类型、字段、方法、属性和事件,并创建相应的元数据表记录项。在创建的元数据中包含一组引用表,它们记录了所引用的内容。表2-2总结了常用的引用元数据表。
摘自《CLR via C# 》第三十四页至三十五页
总结:
在.NET Framework中,元数据被组织成表格结构,这些表格用于描述程序集的详细信息。主要包括元数据定义表、元数据引用表和元数据清单表。
元数据定义表(Metadata Definition Tables)包含定义程序集内所有类型和成员的信息。
元数据引用表(Metadata Reference Tables)包含程序集引用的其他程序集、类型和成员的信息。
元数据清单表(Metadata Manifest Tables)包含程序集的全局信息和管理信息。
元数据长什么样?
想要查看元数据,可以使用ildasm查看程序集元数据:
- 打开 VS命令行工具 (不是普通的CMD,不知道在哪可以打开开始菜单,找到VS的文件夹目录,里面有Developer Command Prompt for VS 2019,我电脑装的是19,这里就用19的了)
- 输入:ildasm 回车即可
- 在ildasm中,点击 “文件 / 打开” 选择自己想查看的程序集即可(编译生成后的exe和dll都可以)
我这里准备了一个测试的控制台应用程序,写了一点测试代码,并且生成了exe
using System;
namespace ConsoleApp1
{
class Program
{
private static int Test_Number = 0;
enum Status
{
Enabled,
Disabled
}
static void Main(string[] args)
{
TestMethod("Hello World!");
}
public static void TestMethod(string msg)
{
Console.WriteLine(msg);
}
}
class YuG_Meta_Test_Class
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
}
}
打开后:
4. 点击 “视图 / 元信息 / 显示!” 即可查看元数据
我这里复制了一些出来:
===========================================================
ScopeName : ConsoleApp1.exe
MVID : {19014EF9-ADE8-4471-9196-737DAAA3EE64}
===========================================================
Global functions
-------------------------------------------------------
Global fields
-------------------------------------------------------
Global MemberRefs
-------------------------------------------------------
TypeDef #1 (02000002)
-------------------------------------------------------
TypDefName: ConsoleApp1.Program (02000002)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000010 [TypeRef] System.Object
Field #1 (04000001)
-------------------------------------------------------
Field Name: Test_Number (04000001)
Flags : [Private] [Static] (00000011)
CallCnvntn: [FIELD]
Field type: I4
Method #1 (06000001) [ENTRYPOINT]
-------------------------------------------------------
MethodName: Main (06000001)
Flags : [Private] [Static] [HideBySig] [ReuseSlot] (00000091)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: SZArray String
1 Parameters
(1) ParamToken : (08000001) Name : args flags: [none] (00000000)
Method #2 (06000002)
-------------------------------------------------------
MethodName: TestMethod (06000002)
Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096)
RVA : 0x0000205e
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: String
1 Parameters
(1) ParamToken : (08000002) Name : msg flags: [none] (00000000)
TypeDef #2 (02000003)
-------------------------------------------------------
TypDefName: ConsoleApp1.YuG_Meta_Test_Class (02000003)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000010 [TypeRef] System.Object
Field #1 (04000002)
-------------------------------------------------------
Field Name: <Name>k__BackingField (04000002)
Flags : [Private] (00000001)
CallCnvntn: [FIELD]
Field type: String
CustomAttribute #1 (0c00000f)
-------------------------------------------------------
CustomAttribute Type: 0a00000f
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Property #1 (17000001)
-------------------------------------------------------
Prop.Name : Name (17000001)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: String
No arguments.
DefltValue:
Setter : (06000005) set_Name
Getter : (06000004) get_Name
0 Others
Property #2 (17000002)
-------------------------------------------------------
Prop.Name : Age (17000002)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: I4
No arguments.
DefltValue:
Setter : (06000007) set_Age
Getter : (06000006) get_Age
0 Others
Property #3 (17000003)
-------------------------------------------------------
Prop.Name : Birthday (17000003)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: ValueClass System.DateTime
No arguments.
DefltValue:
Setter : (06000009) set_Birthday
Getter : (06000008) get_Birthday
0 Others
TypeDef #3 (02000004)
-------------------------------------------------------
TypDefName: Status (02000004)
Flags : [NestedPrivate] [AutoLayout] [Class] [Sealed] [AnsiClass] (00000103)
Extends : 01000011 [TypeRef] System.Enum
EnclosingClass : ConsoleApp1.Program (02000002)
Field #1 (04000005)
-------------------------------------------------------
Field Name: value__ (04000005)
Flags : [Public] [SpecialName] [RTSpecialName] (00000606)
CallCnvntn: [FIELD]
Field type: I4
Field #2 (04000006)
-------------------------------------------------------
Field Name: Enabled (04000006)
Flags : [Public] [Static] [Literal] [HasDefault] (00008056)
DefltValue: (I4) 0
CallCnvntn: [FIELD]
Field type: ValueClass Status
Field #3 (04000007)
-------------------------------------------------------
Field Name: Disabled (04000007)
Flags : [Public] [Static] [Literal] [HasDefault] (00008056)
DefltValue: (I4) 1
CallCnvntn: [FIELD]
Field type: ValueClass Status
TypeRef #1 (01000001)
-------------------------------------------------------
Token: 0x01000001
ResolutionScope: 0x23000001
TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute
MemberRef #1 (0a000001)
-------------------------------------------------------
Member: (0a000001) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
1 Arguments
Argument #1: I4
TypeRef #2 (01000002)
-------------------------------------------------------
Token: 0x01000002
ResolutionScope: 0x23000001
TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
MemberRef #1 (0a000002)
-------------------------------------------------------
Member: (0a000002) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Assembly
-------------------------------------------------------
Token: 0x20000001
Name : ConsoleApp1
Public Key :
Hash Algorithm : 0x00008004
Version: 1.0.0.0
Major Version: 0x00000001
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
Flags : [none] (00000000)
CustomAttribute #1 (0c000001)
-------------------------------------------------------
CustomAttribute Type: 0a000001
CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32)
Length: 8
Value : 01 00 08 00 00 00 00 00 > <
ctor args: (8)
AssemblyRef #1 (23000001)
-------------------------------------------------------
Token: 0x23000001
Public Key or Token: b7 7a 5c 56 19 34 e0 89
Name: mscorlib
Version: 4.0.0.0
Major Version: 0x00000004
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
User Strings
-------------------------------------------------------
70000001 : (12) L"Hello World!"
Coff symbol name overhead: 0
===========================================================
===========================================================
===========================================================
上面是反编译出来的元数据信息,实际要比我展示出来的多很多,但是由于原本的太长了,就挑选了一部分出来,感兴趣的可以自己反编译一下,看看全部的长什么样。
仔细查看上面的元数据,就能发现:在测试代码中定义的类,属性,枚举,方法等都可以在元数据中找到。用编写的测试方法举例,仔细查看IL代码和元数据,你是不是能发现些什么?
main方法的IL代码:
TestMethod方法的元数据:
Method #2 (06000002)
-------------------------------------------------------
MethodName: TestMethod (06000002)
Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096)
RVA : 0x0000205e
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: String
1 Parameters
(1) ParamToken : (08000002) Name : msg flags: [none] (00000000)
仔细查看IL代码中,红色箭头标识的编号,通过这个编号去元数据中查找一下,你是不是就能发现其中的奥秘了。
元数据有什么作用?
元数据在.NET Framework中有许多重要作用:
- 自描述:程序包含自己的描述信息,允许运行时和工具在没有源代码的情况下理解程序结构。
- 反射(Reflection):反射是一个强大的特性,允许程序在运行时检查和操作自身的元数据。
- 类型安全:元数据确保了类型的安全性,运行时可以检查类型兼容性和安全性。
- 序列化和反序列化:元数据帮助进行对象的序列化和反序列化过程。
- 代码生成和编译:编译器和工具可以使用元数据生成和编译代码。
元数据存储在哪?
元数据被存储在程序集(Assembly)中,与IL(中间语言)代码一起打包。具体来说,元数据位于程序集的CLR头(CLI Header)部分。
程序集的结构通常包括以下部分:
- CLR头:包含元数据、模块信息等。
- IL代码:由编译器生成的中间语言代码。
- 资源:嵌入的图像、字符串等资源。
元数据什么时候加载使用?
元数据在以下情况中会被加载:
- 程序集加载时:当程序集被加载到内存中,CLR会读取元数据来了解类型和成员信息。
- 反射操作时:当使用反射API(如Type.GetType、MethodInfo.Invoke等)时,CLR会访问元数据。
- 运行时类型检查时:在进行类型检查、方法调用等操作时,CLR会检查元数据确保操作的合法性。
为什么需要元数据?
元数据对于.NET Framework的运行时和开发过程至关重要,原因包括:
- 运行时类型信息:提供了丰富的类型信息,支持运行时的动态类型检查和方法调用。
- 工具支持:开发工具(如Visual Studio)利用元数据提供智能感知、自动补全等功能。
- 互操作性:元数据使得.NET代码能够与COM、PInvoke等进行互操作。
- 安全性:通过元数据,CLR能够执行类型和安全性验证,防止非法操作。
总结
元数据在.NET Framework中起到了关键作用,提供了丰富的类型信息,支持了开发工具和运行时的多种功能,确保了程序的类型安全和运行时操作的合法性。