7.C#:PE文件,程序集,托管模块,元数据——还是那个Hello world

第一部分:

本文来自:http://www.cnblogs.com/vvjiang/p/5229545.html

好了,还是这张图,还是一样的Hello world。

因为本章其实很多都是讲一些命令行编译啊什么鬼的配置类的东西,要用的时候直接百度或者回头查书就可以了,

所以了解一下也就行了,也没有记录下来,接下来讲得只是我认为很有用的东西。


关于引用

请看上图,MyTest程序集下面有个引用,引用里面大家都知道有很多dll,而我们的源代码中只有那5个using引用某dll里的具体的命名空间。

(引用这些dll只会对编译器造成一点影响,并不会影响最终生成的文件,更不会去影响最终的性能)

也就是说,无论你引用多少个dll,你的源代码中只使用那5个using,那么最终引用的实际上只有包含这5个using的那些dll文件。

再说PE文件,程序集,托管模块,元数据

之前我们说了托管PE文件(也就是那些exe,dll)包括4个部分:PE头、CLR头、元数据和IL代码。

其中PE头是windows要求的标准信息,CLR头是托管模块特有的,包括CLR版本号,模块的入口方法,数字签名,模块内部元数据表的大小和偏移。

 

元数据是由几个表构成的二进制块,有三种元数据表:定义表,引用表和清单表。

所谓定义表,主要就是对本模块内部的一些属性,方法什么的一个描述。

所谓引用表,主要就是对引用的程序集内部的一些属性,方法什么的一个描述。

所谓清单表,主要就是对程序集组成的那部分文件的信息。

 

程序集是进行重用、版本控制和应用安全性设置的基本单元。允许有将类型和资源文件划分到单独的文件中。程序需要加载的程序集数量越少,性能越好,因为这样有助于减小工作集,缓解进程空间地址碎片化

CLR操作的就是程序集,先加载包含清单元数据表的文件,再根据清单来获取程序集中其它文件的名称。

 

好吧,我们主要还是来讲上述四者的关系

结合上图的Hello world程序,来看看我自己的理解:

源代码都会生成托管PE文件,也叫托管模块,就是那些dll和exe,而一个托管模块包含:PE头、CLR头、元数据定义表、元数据引用表和IL代码。

而这些托管模块和一些图片啊什么的资源文件,再加上一个元数据清单表就组合成了程序集(程序集是一个逻辑上的概念)。

一个PE文件也可以仅仅只是一个托管模块,也可以是一个程序集,而区分一个PE文件是托管模块还是程序集的关键就是看它有没有元数据清单表。

那么最通俗的讲,a.EXE引用了b.dll,那么a.exe就是一个程序集,但是b.dll引用了c.dll那么b.dll其实也是一个程序集。

同时a,b,c都是托管PE文件,都是托管模块。

可能饶了点,上述理解中如有纰漏还望看到的大神指出,以免本人在错误的道路上越走越远。




第二部分:

本文来自:http://blog.csdn.net/waxgourd0/article/details/6554681

以下信息都摘自MSDN,很好的解答了什么是元数据,元数据都包括哪些信息,这些信息在元数据中是如何组织管理的,以及,为什么说元数据是“自描述类型”的。

 

      元数据概述:元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (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 方法只返回两个整数参数的和。

 

[c-sharp]  view plain  copy
 print ?
  1. using System;    
  2. public class MyApp  
  3. {  
  4.    public static int Main()  
  5.    {  
  6.       int ValueOne = 10;  
  7.       int ValueTwo = 20;           
  8.       Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));  
  9.       return 0;  
  10.    }  
  11.    public static int Add(int One, int Two)  
  12.    {  
  13.       return (One + Two);  
  14.    }  
  15. }  

 

      当代码运行时,运行库将模块加载到内存并向元数据咨询该类的信息。加载后,运行库对方法的 Microsoft 中间语言 (MSIL) 流执行广泛的分析,将其转换为快速本机指令。运行库根据需要使用实时 (JIT) 编译器将 MSIL 指令转换为本机代码,每次转换一个方法。

      下面的示例显示了从以前代码的 Main 功能生成的部分 MSIL。您可以使用 MSIL 反汇编程序 (Ildasm.exe) 从任何 .NET Framework 应用程序中查看 MSIL 和元数据。

 

[c-sharp]  view plain  copy
 print ?
  1.     
  2. .entrypoint  
  3.       .maxstack  3  
  4.       .locals ([0] int32 ValueOne,  
  5.                [1] int32 ValueTwo,  
  6.                [2] int32 V_2,  
  7.                [3] int32 V_3)  
  8.       IL_0000:  ldc.i4.s   10  
  9.       IL_0002:  stloc.0  
  10.       IL_0003:  ldc.i4.s   20  
  11.       IL_0005:  stloc.1  
  12.       IL_0006:  ldstr      "The Value is: {0}"  
  13.       IL_000b:  ldloc.0  
  14.       IL_000c:  ldloc.1  
  15.       IL_000d:  call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */  
  16.    

 

      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,直到它遇到另一个元数据标记,之后,重复该过程。

      使用元数据,运行库可以访问加载代码并将其处理为本机指令所需的所有信息。以这种方式,元数据使自描述文件、公共类型系统和跨语言继承成为可能。





第三部分:

本文来自:http://www.jb51.net/article/59463.htm

什么是程序集?

1.程序集(assembly)是一个及一个以上托管模块,以及一些资源文件的逻辑组合。
2.程序集是组件复用,以及实施安全策略和版本策略的最小单位。
3.程序集是包含一个或者多个类型定义文件和资源文件的集合。在程序集包含的所有文件中,有一个文件用于保存清单。(清单是元数据部分中一组数据表的集合,其中包含了程序集中一部分文件的名称,描述了程序集的版本,语言文化,发布者,共有导出类型,以及组成该程序集的所有文件)。
4、在编译应用程序中,所创建的CIL代码存储在一个程序集中,程序集包括可执行的应用程序文件(.exe扩展名文件)和其他应用程序使用的库(.dll扩展名文件)。

作为一个单元进行版本控制和部署的一个或多个文件的集合。程序集是.NETFramework 应用程序的主要构造块。所有托管类型和资源都包含在某个程序集内,并被标记为只能在该程序集的内部访问,或者被标记为可以从其他程序集中的代码访问。程序集在安全方面也起着重要作用。代码访问安全系统使用程序集信息来确定为程序集中的代码授予的权限集。

程序集是.NET Framework编程的基本组成部分。(此上是在百度百科的定义)
简单的说,net中的dll和exe文件都是程序集

程序集包含资源文件,类型元数据(描述在代码中定义的每一类型和成员,二进制形式)、IL代码(这些都被装在exe或dll中),每个程序集都有自己的名称、版本等信息。这些信息可以通过AssemblyInfo.cs文件来自己定义。

复制代码代码如下:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 有关程序集的常规信息通过以下
// 特性集控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("AssemblyDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AssemblyDemo")]
[assembly: AssemblyCopyright("Copyright ©  2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 将 ComVisible 设置为 false 使此程序集中的类型
// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型,
// 则将该类型上的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]

// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("e7da9959-0c97-444c-aa40-6d9bbf728068")]

// 程序集的版本信息由下面四个值组成:
//
//      主版本
//      次版本 
//      内部版本号
//      修订号
//
// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,
// 方法是按如下所示使用“*”:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

AssemblyInfo.cs

使用程序集的优点:

程序只需要应用必要的程序集,减少了编码量(例如log4net.dll),程序的尺寸 。
可以在程序集中封装一些代码,提供必要的接口,供引用该程序集的项目使用。   

反射

提到程序集,就不得不说反射。反射就是动态获取程序集中的元数据(提供程序集的类型信息)的功能。也就是动态获取程序集中的元数据来操作类型的。比如咱们使用的vs中的智能提示,就是通过反射获取类的方法、属性的。

Type类是实现反射的一个很重要的类,通过它我们可以获取类中的所有信息包括方法、属性等。可以动态调用类的属性机及方法。

Type是对类的描述。

一个简单的例子新建一个类库MyAssembly添加Person类,生成:

复制代码代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace MyAssembly
{
    public class Person
    {
        private string name;
 
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        private int age;
 
        public int Age
        {
            get { return age; }
            set { age = value; }
        }
        private string gender;
 
        public string Gender
        {
            get { return gender; }
            set { gender = value; }
        }
        public Person(string name, int age, string gender)
        {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        public void SayHi()
        {
            Console.WriteLine("大家好,我是{0},今年{1}岁了。",this.name,this.age);
        }
    }
}

新建一个控制台项目AssemblyDemo,将MyAssembly.dll复制到控制台bin/debug下,接下来通过动态加载程序集MyAssembly.dll获取该程序集下person类的属性和方法。代码如下:

复制代码代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace AssemblyDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //通过反射动态调用另一个程序集中的方法
            //1加载程序集
            string path = AppDomain.CurrentDomain.BaseDirectory;//获取当前.exe执行文件的路径
            path = Path.Combine(path, "MyAssembly.dll");//拼接程序集的路径
            Assembly assembly = Assembly.LoadFile(path);
            //2创建Person类的对象
            Type type = assembly.GetType("MyAssembly.Person");
            object o = Activator.CreateInstance(type,"wolf",22,"未知");//实例化
            //3获取方法的类型
            MethodInfo methodInfo = type.GetMethod("SayHi");
            //4反射调用方法
            methodInfo.Invoke(o, null);
            Console.Read();
        }
    }
}

结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值