原稿地址:
初稿发表日期: 9/19/200五
初稿已经被 Microsoft
删除了,收集进度中发现许多篇章图都不全,那是因为原著的图都不全,所以特收集完整全文。

本页内容

目录

目录

  • 前言
  • CL中华V运行程序(Bootstrap)创制的域
  • 系统域(System
    Domain)
  • 共享域(Shared
    Domain)
  • 默认域(Default
    Domain)
  • 加载器堆(Loader
    Heaps)
  • 品类原理
  • 目的实例
  • 方法表
  • 基实例大小
  • 方法槽表(Method Slot
    Table)
  • 主意描述(MethodDesc)
  • 接口虚表图和接口图(Interface Vtable Map and Interface
    Map)
  • 虚分派(Virtual
    Dispatch)
  • 静态变量(Static
    Variables)
  • EEClass
  • 结论

 

一 加载.NET
程序集

前言

  • SystemDomain, SharedDomain, and DefaultDomain。
  • 指标布局和内部存款和储蓄器细节。
  • 情势表布局。
  • 方法分派(Method dispatching)。

因为国有语言运维时(CL普拉多)即将成为在Windows上开创应用程序的主演级基础架构,
多领悟点关于CL翼虎的深度认识会赞助你营造便捷的, 工业级健壮的应用程序.
在那篇文章中, 我们会浏览,侦察CLMurano的内在精神, 包蕴对象实例布局,
方法表的布局, 方法分派, 基于接口的分摊, 和形形色色的数据结构.

我们会使用由C#写成的格外不难的代码示例,
所以任何对编制程序语言的隐式引用都以以C#语言为对象的.
切磋的局地数据结构和算法会在Microsoft® .NET Framework 二.0中改变,
不过多数的定义是不会变的. 我们会接纳Visual Studio® .NET 二零零三Debugger和debugger extension Son of Strike (SOS)来窥探壹些数量结构.
SOS能够领略CL大切诺基内部的数据结构, 能够dump出有用的音讯. 通篇,
大家会谈谈在Shared Source CLI(SSCLI)中负有相关落到实处的类, 你能够从
下载到它们.

图表1 会扶助您在探寻1些结构的时候到SSCLI中的消息.

ITEM SSCLI PATH
AppDomain sscliclrsrcvmappdomain.hpp
AppDomainStringLiteralMap sscliclrsrcvmstringliteralmap.h
BaseDomain sscliclrsrcvmappdomain.hpp
ClassLoader sscliclrsrcvmclsload.hpp
EEClass sscliclrsrcvmclass.h
FieldDescs sscliclrsrcvmfield.h
GCHeap sscliclrsrcvmgc.h
GlobalStringLiteralMap sscliclrsrcvmstringliteralmap.h
HandleTable sscliclrsrcvmhandletable.h
InterfaceVTableMapMgr sscliclrsrcvmappdomain.hpp
Large Object Heap sscliclrsrcvmgc.h
LayoutKind sscliclrsrcbclsystemruntimeinteropserviceslayoutkind.cs
LoaderHeaps sscliclrsrcincutilcode.h
MethodDescs sscliclrsrcvmmethod.hpp
MethodTables sscliclrsrcvmclass.h
OBJECTREF sscliclrsrcvmtypehandle.h
SecurityContext sscliclrsrcvmsecurity.h
SecurityDescriptor sscliclrsrcvmsecurity.h
SharedDomain sscliclrsrcvmappdomain.hpp
StructLayoutAttribute sscliclrsrcbclsystemruntimeinteropservicesattributes.cs
SyncTableEntry sscliclrsrcvmsyncblk.h
System namespace sscliclrsrcbclsystem
SystemDomain sscliclrsrcvmappdomain.hpp
TypeHandle sscliclrsrcvmtypehandle.h

在我们开端前,请留意:本文提供的消息只对在X八六平台上运转的.NET Framework
一.1一蹴而就(对于Shared Source CLI
一.0也多数适用,只是在少数交互操作的气象下必须注意例外),对于.NET
Framework
二.0会有转移,所以请不要在创设软件时信赖于这几个内部结构的不变性。

图片 1
CLRAV四运营程序(Bootstrap)创立的域

2应用程序域

CLSportage运营程序(Bootstrap)成立的域

在CLPRADO执行托管代码的率先行代码前,会创建三个利用程序域。个中七个对于托管代码甚至CL奔驰M级宿主程序(CLR
hosts)都以不可知的。它们只好由CLGL450运营进程创立,而提供CLMurano运维进程的是shim——mscoree.dll和mscorwks.dll
(在多处理器系统下是mscorsvr.dll)。正如 图2
所示,那些域是系统域(System Domain)和共享域(Shared
Domain),皆以接纳了单件(Singleton)情势。第5个域是缺省应用程序域(Default
AppDomain),它是二个AppDomain的实例,也是绝无仅有的有命名的域。对于简易的CLKoleos宿主程序,比如控制台程序,私下认可的域名由可实施映象文件的名字组成。别的的域能够在托管代码中应用AppDomain.CreateDomain方法创立,只怕在非托管的代码中利用ICO福特ExplorerRuntimeHost接口成立。复杂的宿主程序,比如
ASP.NET,对于特定的网址会依照应用程序的多寡创立八个域。

图 2 由CL奥迪Q5运转程序创设的域 ↓

图片 2

图片 3
系统域(System Domain)

叁解析类型引用

系统域(System Domain)

系统域负责创造和开端化共享域和暗中认可使用程序域。它将系统库mscorlib.dll载入共享域,并且爱慕进程范围里边使用的带有大概显式字符串符号。

字符串驻留(string interning)是 .NET Framework
一.1中的2个优化性情,它的处理办法显得有些昏头转向,因为CLPAJERO未有给程序集机会采纳此个性。固然如此,由于在装有的施用程序域中对3个特定的标志只保留二个应和的字符串,此天性能够节约内部存款和储蓄器空间。

系统域还负责发生进程范围的接口ID,并用来创制各样应用程序域的接口虚表映射图(InterfaceVtableMaps)的接口。系统域在进程中保持跟踪全体域,并落到实处加载和卸载应用程序域的功力。

图片 4
共享域(Shared Domain)


类型

共享域(Shared Domain)

全部不属于别的特定域的代码被加载到系统库SharedDomain.Mscorlib,对于有着应用程序域的用户代码都以必要的。它会被活动加载到共享域中。系统命名空间的骨干类型,如Object,
ValueType, Array, Enum, String, and
Delegate等等,在CL中华V运维程序进度中被事先加载到本域中。用户代码也足以被加载到这些域中,方法是在调用CorBindToRuntimeEx时选拔由CLOdyssey宿主程序钦赐的LoaderOptimization特性。控制台程序也得以加载代码到共享域中,方法是选用System.LoaderOptimizationAttribute性情表明Main方法。共享域还管理贰个选择营地址作为目录的顺序集映射图,此映射图作为管理共享程序集依赖关系的查找表,那几个程序集被加载到暗中同意域(DefaultDomain)和此外在托管代码中开创的接纳程序域。非共享的用户代码被加载到暗中认可域。

图片 5
默认域(Default Domain)

5内部存款和储蓄器分配

默认域(Default Domain)

默许域是运用程序域(AppDomain)的两个实例,一般的应用程序代码在里面运维。固然有个别应用程序须求在运作时创制额外的使用程序域(比如有个别使用插件,plug-in,框架结构只怕拓展重大的运作时代码生成工作的应用程序),超越四分之贰的应用程序在运行时期只创设一个域。全部在此域运转的代码都以在域层次上有上下文限制。假诺一个应用程序有多少个使用程序域,任何的域间访问会通过.NET
Remoting代理。额外的域内上下文限制音信能够使用System.ContextBoundObject派生的项目创设。每种应用程序域有投机的安全描述符(SecurityDescriptor),安全上下文(SecurityContext)和暗中认可上下文(DefaultContext),还有温馨的加载器堆(高频堆,低频堆和代办堆),句柄表,接口虚表管理器和程序集缓存。

图片 6
加载器堆(Loader Heaps)

六类型、对象、线程栈、托管堆在运营时的交互调换

加载器堆(Loader Heaps)

加载器堆的效率是加载差异的运行时CLSportage部件和优化在域的上上下下生命期内设有的部件。这一个堆的滋长基于可预测块,那样能够使碎片最小化。加载器堆差别于垃圾回收堆(大概对称多处理器上的多个堆),垃圾回收堆保存对象实例,而加载器堆同时保留类型系统。平常访问的部件如方法表,方法描述,域描述和接口图,分配在屡次堆上,而较少访问的数据结构如EEClass和类加载器及其查找表,分配在低频堆。代理堆保存用于代码访问安全性(code
access security, CAS)的代理部件,如COM封装调用和平台调用(P/Invoke)。

从高层次领悟域后,我们准备看看它们在三个归纳的应用程序的左右文中的情理细节,见
图3。大家在程序运维时停在mc.Method一(),然后利用SOS调节和测试器扩充命令DumpDomain来输出域的信息。(请查看
Son of
Strike
询问SOS的加载音讯)。那里是编写制定后的出口:

图3 Sample1.exe

!DumpDomain
System Domain: 793e9d58, LowFrequencyHeap: 793e9dbc,
HighFrequencyHeap: 793e9e14, StubHeap: 793e9e6c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40

Shared Domain: 793eb278, LowFrequencyHeap: 793eb2dc,
HighFrequencyHeap: 793eb334, StubHeap: 793eb38c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40

Domain 1: 149100, LowFrequencyHeap: 00149164,
HighFrequencyHeap: 001491bc, StubHeap: 00149214,
Name: Sample1.exe, Assembly: 00164938 [Sample1],
ClassLoader: 00164a78

using System;

public interface MyInterface1
{
    void Method1();
    void Method2();
}
public interface MyInterface2
{
    void Method2();
    void Method3();
}

class MyClass : MyInterface1, MyInterface2
{
    public static string str = "MyString";
    public static uint   ui = 0xAAAAAAAA;
    public void Method1() { Console.WriteLine("Method1"); }
    public void Method2() { Console.WriteLine("Method2"); }
    public virtual void Method3() { Console.WriteLine("Method3"); }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        MyInterface1 mi1 = mc;
        MyInterface2 mi2 = mc;

        int i = MyClass.str.Length;
        uint j = MyClass.ui;

        mc.Method1();
        mi1.Method1();
        mi1.Method2();
        mi2.Method2();
        mi2.Method3();
        mc.Method3();
    }
}

大家的控制台程序,Sample一.exe,被加载到三个名字为”Sample一.exe”的应用程序域。Mscorlib.dll被加载到共享域,然则因为它是主导系统库,所以也在系统域中列出。每一个域会分配2个屡屡堆,低频堆和代理堆。系统域和共享域使用相同的类加载器,而暗中同意应用程序使用本人的类加载器。

出口未有显得加载器堆的保留尺寸和已交给尺寸。高频堆的伊始化大小是32KB,每一回提交4KB。SOS的出口也尚无展现接口虚表堆(InterfaceVtableMap)。各种域有3个接口虚表堆(简称为IVMap),由友好的加载器堆在域初叶化阶段创设。IVMap保留大小是4KB,初叶时交由4KB。大家将会在继续部分钻探项目布局时切磋IVMap的意思。

图2
展现暗许的经过堆,JIT代码堆,GC堆(用于小目的)和大指标堆(用于大小也就是依旧超越85000字节的指标),它注脚了那几个堆和加载器堆的语义分歧。即时(just-in-time,
JIT)编写翻译器发生x八陆指令并且保留到JIT代码堆中。GC堆和大目的堆是用来托管对象实例化的污物回收堆。

图片 7
花色原理

  本文将解释 PE、Windows
加载器、应用程序域、程序集清单、元数据、类型、对象、线程栈、托管堆等,与运作时的相互关系。由此,小编首先写了2个简便
Demo 用于调节和测试,其代码如下:

项目原理

花色是.NET编制程序中的基本单元。在C#中,类型能够动用class,struct和interface关键字展开宣示。抢先伍3%档次由程序员显式创制,不过,在特意的互相操作(interop)情状和长途对象调用(.NET
Remoting)场所中,.NET
CLLacrosse会隐式的发出类型,这一个发生的类型涵盖COM和周转时可调用封装及传输代理(Runtime
Callable Wrappers and Transparent Proxies)。

大家经过四个包蕴对象引用的栈发轫商讨.NET类型原理(典型地,栈是一个指标实例开首生命期的地点)。
图4中展现的代码包蕴2个简短的先后,它有3个控制台的入口点,调用了二个静态方法。Method一开立1个SmallClass的类别实例,该类型涵盖三个字节数组,用于演示如何在大指标堆创设对象。即使那是一段无聊的代码,可是足以扶助我们开始展览座谈。

图4 Large Objects and Small Objects

using System;

class SmallClass
{
    private byte[] _largeObj;
    public SmallClass(int size)
    {
        _largeObj = new byte[size];
        _largeObj[0] = 0xAA;
        _largeObj[1] = 0xBB;
        _largeObj[2] = 0xCC;
    }

    public byte[] LargeObj
    {
        get { return this._largeObj; }
    }
}

class SimpleProgram
{
    static void Main(string[] args)
    {
        SmallClass smallObj = SimpleProgram.Create(84930,10,15,20,25);
        return;
    }

    static SmallClass Create(int size1, int size2, int size3,
        int size4, int size5)
    {
        int objSize = size1 + size2 + size3 + size4 + size5;
        SmallClass smallObj = new SmallClass(objSize);
        return smallObj;
    }
}

图5 呈现了甘休在Create方法”return smallObj;”
代码行断点时的fastcall栈结构(fastcall时.NET的调用规范,它注明在或者的景象下将函数参数通过寄存器传递,而其他参数遵照从右到左的顺序入栈,然后由被调用函数完结出栈操作)。本地值类型变量objSize内含在栈结构中。引用类型变量如smallObj以一定大小(四字节DWO福睿斯D)保存在栈中,包括了在一般GC堆中分红的靶子的地方。对于守旧C++,那是目的的指针;在托管世界中,它是目的的引用。不管如何,它包罗了叁个指标实例的地方,大家将应用术语对象实例(ObjectInstance)描述对象引用指向地址地方的数据结构。

图5 SimpleProgram的栈结构和堆

图片 8

诚如GC堆上的smallObj对象实例包涵三个名称为 _largeObj
的字节数组(注意,图中显得的深浅为85016字节,是实际上的储备大小)。CLQashqai对当先或等于八4000字节的对象的拍卖和小目的不一致。大指标在大目的堆(LOH)上抽成,而小目的在一般GC堆上成立,那样能够优化对象的分红和回收。LOH不会压缩,而GC堆在GC回收时展开削减。还有,LOH只会在完全GC回收时被回收。

smallObj的靶子实例包蕴类型句柄(TypeHandle),指向对应品种的方法表。每种注解的门类有贰个方法表,而同等类型的富有目的实例都针对同二个方法表。它涵盖了类其他特色音讯(接口,抽象类,具体类,COM封装和代办),达成的接口数目,用于接口分派的接口图,方法表的槽(slot)数目,指向相应达成的槽表。

措施表指向3个名称为EEClass的主要数据结构。在情势表成立前,CL陆风X8类加载器从元数据中创制EEClass。
图4中,SmallClass的艺术表指向它的EEClass。那些组织指向它们的模块和次序集。方法表和EEClass一般分配在共享域的加载器堆。加载器堆和应用程序域关联,那里涉及的数据结构一旦被加载到里头,就直到应用程序域卸载时才会未有。而且,默许的施用程序域不会被卸载,所以那些代码的生存期是停止CLTucson关闭截止。

图片 9
目的实例

using System;

namespace CLRTest
{
    public class Circle
    {
        public double Radius { get; set; }

        public Circle() { }

        public Circle(double r)
        {
            this.Radius = r;
        }

        public double GetCircumference()
        {
            return 2 * Math.PI * Radius;
        }

        public double GetArea()
        {
            return Math.PI * Math.Pow(this.Radius, 2.0);
        }

        public override string ToString()
        {
            return string.Format("半径:{0}  周长:{1}  面积:{2}", this.Radius, this.GetCircumference(), this.GetArea());
        }
    }
}

using System;

namespace CLRTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Circle circle = new Circle(4.0);
            Console.WriteLine(circle.ToString());
            Console.ReadKey();
        }
    }
}

对象实例

正如小编辈说过的,全数值类型的实例只怕隐含在线程栈上,只怕隐含在 GC
堆上。全数的引用类型在 GC 堆大概 LOH 上创设。图 6
显示了贰个典型的目的布局。八个对象能够通过以下途径被引述:基于栈的一部分变量,在相互操作照旧平台调用意况下的句柄表,寄存器(执行措施时的
this 指针和章程参数),拥有终结器( finalizer )方法的对象的终结器队列。
OBJECTREF 不是指向指标实例的启幕地方,而是有两个 DWOTucsonD 的偏移量( 四字节)。此 DWO福特ExplorerD 称为对象头,保存三个针对性 SyncTableEntry 表的目录(从 壹开首计数的 syncblk
编号。因为通过索引举办连接,所以在急需扩大表的尺寸时, CLCR-V可以在内部存款和储蓄器中移动那一个表。 SyncTableEntry 维护一个反向的弱引用,以便 CL奥迪Q5可以跟踪 SyncBlock 的全数权。弱引用让 GC
能够在未曾其它强引用存在时回收对象。 SyncTableEntry 还保留了二个对准
SyncBlock
的指针,包括了很少须求被三个目的的兼具实例使用的有效性的音讯。这一个消息包括对象锁,哈希编码,任何转换层
(thunking) 数据和应用程序域的目录。对于当先3/6的对象实例,不会为实际的
SyncBlock 分配内部存款和储蓄器,而且 syncblk 编号为 0 。那点在实践线程境遇如
lock(obj) 可能 obj.GetHashCode 的话语时会爆发变化,如下所示:

SmallClass obj = new SmallClass()
// Do some work here
lock(obj) { /* Do some synchronized work here */ }
obj.GetHashCode();

图 6 对象实例布局
图片 10

在以上代码中, smallObj 会选用 0 作为它的开首的 syncblk 编号。 lock
语句使得 CL大切诺基 创立多少个 syncblk 入口并采纳相应的数值更新对象头。因为 C#
的 lock 关键字会扩大为 try-finally 语句并使用 Monitor 类,一个当做同步的
Monitor 对象在 syncblk 上开创。堆 GetHashCode
的调用会利用对象的哈希编码扩展 syncblk 。
在 SyncBlock 中有其余的域,它们在 COM 交互操作和封送委托( marshaling
delegates )到非托管代码时选拔,可是那和特出的靶子用处毫无干系。
品类句柄紧跟在目的实例中的 syncblk
编号后。为了保全再三再四性,小编会在印证实例变量后探讨类型句柄。实例域(
Instance 田野先生)的变量列表紧跟在类型句柄后。默许情形下,实例域会以内部存款和储蓄器最可行使用的法子排列,那样只要求最少的作为对齐的填充字节。
7
的代码显示了 SimpleClass 包蕴有壹部分不及尺寸的实例变量。

图 7 SimpleClass with Instance Variables

class SimpleClass
{
    private byte b1 = 1;                // 1 byte
    private byte b2 = 2;                // 1 byte
    private byte b3 = 3;                // 1 byte
    private byte b4 = 4;                // 1 byte
    private char c1 = 'A';              // 2 bytes
    private char c2 = 'B';              // 2 bytes
    private short s1 = 11;              // 2 bytes
    private short s2 = 12;              // 2 bytes
    private int i1 = 21;                // 4 bytes
    private long l1 = 31;               // 8 bytes
    private string str = "MyString"; // 4 bytes (only OBJECTREF)

    //Total instance variable size = 28 bytes 

    static void Main()
    {
        SimpleClass simpleObj = new SimpleClass();
        return;
    }
}

图 8 展现了在 Visual Studio 调节和测试器的内部存款和储蓄器窗口中的多少个 SimpleClass
对象实例。大家在图 7 的 return 语句处设置了断点,然后利用 ECX
寄存器保存的 simpleObj 地址在内部存款和储蓄器窗口显示对象实例。前 四 个字节是 syncblk
编号。因为大家一向不用其余共同代码应用此实例(也从没访问它的哈希编码),
syncblk 编号为 0 。保存在栈变量的对象实例,指向初步地方的 几个字节的偏移处。字节变量 b一,b二,b三 和 b4 被贰个接一个的排列在联合署名。五个short 类型变量 s一 和 s二 也被排列在共同。字符串变量 str 是八个 4 字节的
OBJECTREF ,指向 GC
堆中分配的莫过于的字符串实例。字符串是八个尤其的种类,因为全数包括同样文字标记的字符串,会在程序集加载到进度时指向三个大局字符串表的一样实例。那些进程称为字符串驻留(
string interning ),设计指标是优化内部存款和储蓄器的行使。我们前边曾经提过,在 NET
Framework 一.一 中,程序集不可能选拔是还是不是利用这些进程,就算未来版本的 CL昂Cora也许会提供这么的力量。

图 8 Debugger Memory Window for Object Instance
图片 11

为此私下认可景况下,成员变量在源代码中的词典顺序没有在内部存款和储蓄器中保持。在相互操作的动静下,词典顺序必须被封存到内部存款和储蓄器中,那时能够利用
StructLayoutAttribute 性情,它有二个 LayoutKind 的枚举类型作为参数。
LayoutKind.Sequential 能够为被封送( marshaled
)数据保持词典顺序,尽管在 .NET Framework 1.1中,它从不影响托管的布局(可是 .NET Framework 贰.0
可能会这么做)。在彼此操作的情事下,假使你实在需求额外的填充字节和突显的控制域的次第,
LayoutKind.Explicit 能够和域层次的 FieldOffset 特性壹起行使。

看完底层的内部存款和储蓄器内容后,大家应用 SOS 看看对象实例。1个立竿见影的指令是
DumpHeap
,它能够列出全部的堆内容和四个特别类型的有着实例。无需依靠寄存器,
DumpHeap 可以来得大家创造的绝无仅有二个实例的地方。

!DumpHeap -type SimpleClass
Loaded Son of Strike data table version 5 from
"C:WINDOWSMicrosoft.NETFrameworkv1.1.4322mscorwks.dll"
 Address       MT     Size
00a8197c 00955124       36
Last good object: 00a819a0
total 1 objects
Statistics:
      MT    Count TotalSize Class Name
  955124        1        36 SimpleClass

指标的总大小是 36 字节,不管字符串多大, SimpleClass 的实例只含有一个DWORubiconD 的目的引用。 SimpleClass 的实例变量只占用 2八 字节,其余 8个字节包涵项目句柄( 四 字节)和 syncblk 编号( 四 字节)。找到 simpleObj
实例的地址后,大家得以采取 DumpObj 命令输出它的内容,如下所示:

!DumpObj 0x00a8197c
Name: SimpleClass
MethodTable 0x00955124
EEClass 0x02ca33b0
Size 36(0x24) bytes
FieldDesc*: 00955064
      MT    Field   Offset                 Type       Attr    Value Name
00955124  400000a        4         System.Int64   instance      31 l1
00955124  400000b        c                CLASS   instance 00a819a0 str
    << some fields omitted from the display for brevity >>
00955124  4000003       1e          System.Byte   instance        3 b3
00955124  4000004       1f          System.Byte   instance        4 b4

正如以前说过, C# 编译器对于类的默许布局使用 LayoutType.Auto
(对于协会选用 LayoutType.Sequential
);因而类加载注重新排列实例域以最小化填充字节。大家得以动用 ObjSize
来输出包罗被 str 实例占用的长空,如下所示:

!ObjSize 0x00a8197c
sizeof(00a8197c) =       72 (    0x48) bytes (SimpleClass)

假若您从指标图的全局大小( 7二 字节)减去 SimpleClass 的大小( 3陆字节),就能够收获 str 的轻重缓急,即 3陆 字节。让我们输出 str
实例来表达那几个结果:

!DumpObj 0x00a819a0
Name: System.String
MethodTable 0x009742d8
EEClass 0x02c4c6c4
Size 36(0x24) bytes

若是你将字符串实例的大小(3六字节)加上SimpleClass实例的大小(3陆字节),就能够收获ObjSize命令报告的总大小72字节。

请留心ObjSize不包括syncblk结构占用的内部存款和储蓄器。而且,在.NET Framework
1.第11中学,CLTiggo不知情非托管财富占用的内部存款和储蓄器,如GDI对象,COM对象,文件句柄等等;因而它们不会被那个命令报告。

本着方法表的项目句柄在syncblk编号后分配。在指标实例创设前,CL锐界查看加载类型,假诺没有找到,则开始展览加载,获得方法表地址,创制对象实例,然后把品种句柄值追加到对象实例中。JIT编写翻译器发生的代码在进展格局分派时利用项目句柄来稳定方法表。CLTiggo在要求史能够透过措施表反向访问加载类型时行使项目句柄。

Son of Strike
SOS调节和测试器扩大程序用于本文化的突显CL奇骏数据结构的内容,它是 .NET
Framework 安装程序的一片段,位于
%windir%\Microsoft.NET\Framework\v1.1.4322。SOS加载到进程从前,在
Visual Studio 中启用托管代码调节和测试。 添加 SOS.dll
所在的公文夹到PATH环境变量中。 加载 SOS.dll, 然后安装多个断点, 打开
Debug|Windows|Immediate。然后在 Immediate 窗口中履行 .load
sos.dll。使用 !help
获取调节和测试相关的片段限令,关于SOS越来越多消息,参考这里。

图片 12
方法表

一 加载.NET 程序集

方法表

各种类和实例在加载到利用程序域时,会在内部存款和储蓄器中经过艺术表来表示。那是在对象的率先个实例创设前的类加载活动的结果。对象实例表示的是状态,而艺术表表示了作为。通过EEClass,方法表把对象实例绑定到被语言编写翻译器发生的映射到内部存款和储蓄器的元数据结构(metadata
structures)。方法表包蕴的新闻和外挂的消息方可经过System.Type访问。指向方法表的指针在托管代码中得以因而Type.RuntimeTypeHandle属性得到。对象实例包蕴的种类句柄指向方法表起第3地方的舞狮处,偏移量暗许情状下是1二字节,包蕴了GC音信。大家不打算在此处对其开展切磋。

图 9
展现了办法表的出众布局。大家会注明项目句柄的片段要害的域,不过对于截然的列表,请参见此图。让大家从基实例大小(Base
Instance Size)初叶,因为它直接涉及到运营时的内部存款和储蓄器状态。

图 9 方法表布局

图片 13

图片 14
基实例大小

  在Windows上运转的次序能够透过三种分裂的不二诀要开始展览运维。Windows
负责处理全体的相关工作,包罗安装进程地址空间、加载可执行程序,以及提醒处理器起首推行等。当电脑初始推行顺序指令时,它将平素施行下去,直到进度退出。

基实例大小

基实例大小是由类加载器总括的对象的尺寸,基于代码中证明的域。在此之前曾经研究过,当前GC的落到实处内需三个至少1二字节的对象实例。要是二个类没有概念任何实例域,它起码含有额外的四个字节。其它的7个字节被对象头(大概蕴含syncblk编号)和花色句柄占用。再说二次,对象的尺寸会遭到StructLayoutAttribute的震慑。

看看图3中呈现的MyClass(有四个接口)的主意表的内存快速照相(Visual
Studio .NET
2003内部存款和储蓄器窗口),将它和SOS的出口举办相比。在图9中,对象大小位于肆字节的舞狮处,值为1二(0x0000000C)字节。以下是SOS的DumpHeap命令的出口:

!DumpHeap -type MyClass
 Address       MT     Size
00a819ac 009552a0       12
total 1 objects
Statistics:
    MT  Count TotalSize Class Name
9552a0      1        12    MyClass

图片 15
措施槽表(Method Slot Table)

  以后扩充大家对 PE 文件的认识,PE
格式是 Windows
可执行程序的文件格式,可执行程序包含:*.exe、*.dll、*.obj、*.sys
等。为了协理.NET,在 PE
文件格式中加进了对先后集的支撑,PE文件格式如下:

主意槽表(Method Slot Table)

在点子表中富含了1个槽表,指向各样艺术的叙述(MethodDesc),提供了品种的行为能力。方法槽表是依照方法完结的线性链表,依据如下顺序排列:继承的虚方法,引进的虚方法,实例方法,静态方法。

类加载器在脚下类,父类和接口的元数据中遍历,然后创立方法表。在排列进度中,它替换全部的被遮住的虚方法和被隐形的父类方法,创设新的槽,在需求时复制槽。槽复制是不能缺少的,它能够让每种接口有谈得来的微小的vtable。不过被复制的槽指向平等的物理完结。MyClass包罗接口方法,叁个类构造函数(.cctor)和对象构造函数(.ctor)。对象构造函数由C#编写翻译器为保有未有显式定义构造函数的靶子自动生成。因为大家定义并开始化了1个静态变量,编写翻译器会转移1个类构造函数。图10来得了MyClass的章程表的布局。布局展现了13个办法,因为Method二槽为接口IVMap举行了复制,下边大家会开始展览商量。图11来得了MyClass的方法表的SOS的出口。

图10 MyClass MethodTable Layout
图片 16

图11 SOS Dump of MyClass Method Table

!DumpMT -MD 0x9552a0
  Entry  MethodDesc  Return Type       Name
0097203b 00972040    String            System.Object.ToString()
009720fb 00972100    Boolean           System.Object.Equals(Object)
00972113 00972118    I4                System.Object.GetHashCode()
0097207b 00972080    Void              System.Object.Finalize()
00955253 00955258    Void              MyClass.Method1()
00955263 00955268    Void              MyClass.Method2()
00955263 00955268    Void              MyClass.Method2()
00955273 00955278    Void              MyClass.Method3()
00955283 00955288    Void              MyClass..cctor()
00955293 00955298    Void              MyClass..ctor()

其它项指标起来五个点子总是ToString, Equals, GetHashCode, and
Finalize。那些是从System.Object继承的虚方法。Method二槽被实行了复制,但是都指向相同的艺术描述。代码突显定义的.cctor和.ctor会分别和静态方法和实例方法分在1组。

图片 17
办法描述(MethodDesc)

图片 18

措施描述(MethodDesc)

办法描述(MethodDesc)是CL大切诺基知道的措施实现的贰个封装。有几体系型的不二诀要描述,除了用于托管完成,分别用于分歧的互动操作实现的调用。在本文中,大家只考查图3代码中的托管方法描述。方法描述在类加载进度中爆发,初阶化为指向IL。各种方法描述包蕴1个预编写翻译代理(PreJitStub),负责触发JIT编写翻译。图12来得了多个第一名的布局,方法表的槽实际上指向代理,而不是实在的章程描述数据结构。对于实际的措施描述,那是-5字节的舞狮,是每一种方法的八个附加字节的一局地。那6个字节包罗了调用预编写翻译代理程序的命令。五字节的偏移能够从SOS的DumpMT输出从察看,因为方法描述总是方法槽表指向的岗位后边的八个字节。在率先次调用时,会调用JIT编写翻译程序。在编写翻译实现后,包含调用指令的多少个字节会被跳转到JIT编写翻译后的x八陆代码的无偿跳转指令覆盖。

图 12艺术描述

图片 19

图12的法子表槽指向的代码进行反汇编,呈现了对预编写翻译代理的调用。以下是在
Method贰 被JIT编写翻译前的反汇编的简化展现。

Method2:

!u 0x00955263
Unmanaged code
00955263 call        003C3538        ;call to the jitted Method2()
00955268 add         eax,68040000h   ;ignore this and the rest
                                     ;as !u thinks it as code

最近大家举办此办法,然后反汇编相同的地方:

!u 0x00955263
Unmanaged code
00955263 jmp     02C633E8        ;call to the jitted Method2()
00955268 add     eax,0E8040000h  ;ignore this and the rest
                                 ;as !u thinks it as code

在此地方,唯有起始多个字节是代码,剩余字节包括了Method二的措施描述的多寡。“!u”命令不知底这点,所以生成的是无规律的代码,你能够忽略5个字节后的兼具东西。

CodeOrIL在JIT编写翻译前包括IL中艺术完成的相持虚地址(Relative Virtual
Address
,昂科拉VA)。此域用作标志,表示是还是不是IL。在按要求编写翻译后,CL奥德赛使用编写翻译后的代码地址更新此域。让大家从列出的函数中接纳二个,然后用DumpMT命令分别出口在JIT编译前后的章程描述的始末:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
IL RVA : 00002068

编写翻译后,方法描述的内容如下:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
Method VA : 02c633e8

主意的这么些标志域的编码包蕴了法子的品种,例如静态,实例,接口方法只怕COM达成。让大家看方法表其它二个错综复杂的地点:接口达成。它包裹了布局过程具有的复杂,让托管环境觉得那或多或少看起来大致。然后,大家将注解接口如何开始展览布局和依据接口的法子分派的贴切工作办法。

图片 20
接口虚表图和接口图

  为了协理PE影像的实行,在PE的头包涵了二个域称为
AddressOfEntryPoint。这几个域表示 PE
文件的入口点(EntryPoint)的职位。在.NET程序集中,这么些值指向.text
段中的一小段存根(stub)代码(“JMP _CorExeMain”)。当.NET
编写翻译器生成程序集时,它会在 PE
文件中加进三个数码目录项。具体来说,这些数目目录项的目录为
15,个中包罗了 CLHighlander 头的岗位和大小。然后,依据这些职责在 PE
文件中找到位于.text 段中的 CLHaval 头。在 CL翼虎 头中隐含了叁个构造
IMAGE_COR20_HEADE安德拉。在这几个布局中包涵了累累新闻,例如托管代码应用程序入口点,目的CL奥德赛的主版本号和从版本号,以及程序集的强名称签名等。根据那么些布局中涵盖的音信,Windows
能够领会要加载哪个版本的 CL卡宴 以及有关程序集作者的有个别新闻。在.text
段中还包罗了先后集的元数据表,IL以及非托管运营存根码。非托管运维存根码包蕴了由
Windows 加载器执行以运营 PE 文件执行的代码。

接口虚表图和接口图(Interface Vtable Map and Interface Map)

在点子表的第一2字节偏移处是多个注重的指针,接口虚表(IVMap)。如图9所示,接口虚表指向二个使用程序域层次的映射表,该表以进度层次的接口ID作为目录。接口ID在接口类型第三回加载时成立。每种接口的达成都在接口虚表中有三个记录。如果MyInterface1被四个类达成,在接口虚表表中就有两个记录。该记录会反向指向MyClass方法表内含的子表的初阶地点,如图9所示。那是接口方法分派产生时行使的引用。接口虚表是遵照方法表内含的接口图信息创立,接口图在点子表布局进度中基于类的元数据成立。一旦类型加载成功,只有接口虚表用于方法分派。

第一捌字节地方的接口图会指向内含在措施表中的接口消息记录。在那种景观下,对MyClass完结的七个接口中的每多少个都有两条记下。第二条接口新闻记录的上马四个字节指向MyInterface壹的品种句柄(见图9图10)。接着的WOTiggoD(贰字节)被二个标志占用(0意味从父类派生,一表示由目前类达成)。在评释后的WOPAJEROD是三个始发槽(Start
Slot),被类加载器用来布局接口完毕的子表。对于MyInterface2,伊始槽的值为四(从0早先编号),所以槽伍和6指向实现;对于MyInterface2,初阶槽的值为陆,所以槽七和八指向完成。类加载器会在必要时复制槽来发出如此的效应:每种接口有投机的完毕,不过物理映射到同壹的措施描述。在MyClass中,MyInterface一.Method二和MyInterface贰.Method二会指向相同的贯彻。

基于接口的法门分派通过接口虚表举行,而直白的章程分派通过保留在依次槽的措施描述地址实行。如在此之前谈到,.NET框架使用fastcall的调用约定,初叶三个参数在也许的时候一般经过ECX和EDX寄存器传递。实例方法的率先个参数总是this指针,所以经过ECX寄存器传送,能够在“mov
ecx,esi”语句看到这点:

mi1.Method1();
mov    ecx,edi                 ;move "this" pointer into ecx
mov    eax,dword ptr [ecx]     ;move "TypeHandle" into eax
mov    eax,dword ptr [eax+0Ch] ;move IVMap address into eax at offset 12
mov    eax,dword ptr [eax+30h] ;move the ifc impl start slot into eax
call   dword ptr [eax]         ;call Method1

mc.Method1();
mov    ecx,esi                 ;move "this" pointer into ecx
cmp    dword ptr [ecx],ecx     ;compare and set flags
call   dword ptr ds:[009552D8h];directly call Method1

那些反汇编展现了第二手调用MyClass的实例方法未有采用偏移。JIT编写翻译器把办法描述的地址直接写到代码中。基于接口的分担通过接口虚表产生,和一贯分派相比供给有的外加的授命。二个限令用来博取接口虚表的地址,另三个赢得情势槽表中的接口达成的上马槽。而且,把1个目的实例转换为接口只供给拷贝this指针到对象的变量。在图第22中学,语句“mi一=mc”使用一个限令把mc的靶子引用拷贝到mi1。

图片 21
虚分派(Virtual Dispatch)

  当 Windows 加载多个.NET
程序集时,mscoree.dll
的_CorExeMain(或者是_CorDllMain,取决于加载的是可执行文件还是库)
函数被第3个调用,以运转 CL路虎极光。 mscoree.dll 在开发银行 CL翼虎时将实施一密密麻麻操作:

虚分派(Virtual Dispatch)

最近大家看看虚分派,并且和依据接口的摊派实行比较。以下是图3中MyClass.Method3的虚函数调用的反汇编代码:

mc.Method3();
Mov    ecx,esi               ;move "this" pointer into ecx
Mov    eax,dword ptr [ecx]   ;acquire the MethodTable address
Call   dword ptr [eax+44h]   ;dispatch to the method at offset 0x44

虚分派总是通过三个恒定的槽编号产生,和艺术表指针在一定的类(类型)实现层次非亲非故。在格局表布局时,类加载器用覆盖的子类的完成代替父类的兑现。结果,对父对象的办法调用被分摊到子对象的贯彻。反汇编呈现了分派通过8号槽产生,能够在调节和测试器的内部存款和储蓄器窗口(如图十所示)和DumpMT的出口看到那或多或少。

图片 22
静态变量

  (1) 通过翻看 PE
文件中的元数据(具体来说是 CLXC90 头中的 MajorRuntimeVersion 和
MinorRuntimeVersion)找出.NET 程序集是遵照哪个版本的 CL卡宴 营造的。

静态变量(Static Variables)

静态变量是办法表数据结构的要害组成都部队分。作为艺术表的一有的,它们分配在措施表的槽数组后。全部的原本静态类型是内联的,而对此组织和引用的类别的静态值对象,通在句柄表中开创的靶子引用来针对。方法表中的对象引用指向应用程序域的句柄表的目的引用,它引用了堆上创立的靶子实例。一旦创建后,句柄表内的对象引用会使堆上的对象实例保持生存,直到应用程序域被卸载。在图9
中,静态字符串变量str指向句柄表的目的引用,后者指向GC堆上的MyString。

图片 23
EEClass

  (2) 找出 OS 中国中国科学技术大学学学版本 CL路虎极光的路径。

EEClass

EEClass在章程表创立前开头生活,它和方法表组成起来,是项目注明的CL奥迪Q7版本。实际上,EEClass和办法表逻辑上是四个数据结构(它们壹起表示三个品类),只可是因为运用频度的不一样而被分开。常常使用的域放在方法表,而不平日应用的域在EEClass中。那样,需求被JIT编写翻译函数使用的消息(如名字,域和偏移)在EEClass中,不过运维时需求的新闻(如虚表槽和GC音讯)在章程表中。

对每2个类型会加载三个EEClass到应用程序域中,包含接口,类,抽象类,数组和布局。每一个EEClass是二个被执行引擎跟踪的树的节点。CLOdyssey使用这几个网络在EEClass结构中浏览,其目标包蕴类加载,方法表布局,类型验证和类型转换。EEClass的子-父关系基于继承层次建立,而父-子关系基于接口层次和类加载顺序的咬合。在履行托管代码的长河中,新的EEClass节点被投入,节点的涉及被填补,新的涉及被确立。在互连网中,相邻的EEClass还有一个水准的关联。EEClass有七个域用于管理被加载类型的节点关系:父类(Parent
Class),相邻链(sibling chain)和子链(children
chain)。关于图4中的MyClass上下文中的EEClass的语义,请参考图13

图13只展示了和那个探究相关的一些域。因为我们忽略了布局中的一些域,大家从未在图中正好彰显偏移。EEClass有2个直接的对于艺术表的引用。EEClass也针对在暗中同意使用程序域的壹再堆分配的不2诀要描述块。在章程表创立时,对进度堆上分配的域描述列表的叁个引用提供了域的布局消息。EEClass在运用程序域的低频堆分配,那样操作系统能够越来越好的进行内部存款和储蓄器分页管理,由此收缩了工作集。

图13 EEClass 布局

图片 24

图13中的其它域在MyClass(图3)的上下文的意思不言自明。大家今后探访使用SOS输出的EEClass的的确的情理内部存款和储蓄器。在mc.Method壹代码行设置断点后,运营图三的先后。首先应用命令Name二EE获得MyClass的EEClass的地点。

!Name2EE C:WorkingtestClrInternalsSample1.exe MyClass

MethodTable: 009552a0
EEClass: 02ca3508
Name: MyClass

Name二EE的率先个参数时模块名,能够从DumpDomain命令获得。以往我们收获了EEClass的地点,我们输出EEClass:

!DumpClass 02ca3508
Class Name : MyClass, mdToken : 02000004, Parent Class : 02c4c3e4
ClassLoader : 00163ad8, Method Table : 009552a0, Vtable Slots : 8
Total Method Slots : a, NumInstanceFields: 0,
NumStaticFields: 2,FieldDesc*: 00955224

      MT    Field   Offset  Type           Attr    Value    Name
009552a0  4000001   2c      CLASS          static 00a8198c  str
009552a0  4000002   30      System.UInt32  static aaaaaaaa  ui

图13和DumpClass的出口看起来完全平等。元数据令牌(metadata
token,mdToken)表示了在模块PE文件中映射到内部存储器的元数据表的MyClass索引,父类指向System.Object。从相邻链指向名字为Program的EEClass,能够领悟图1三来得的是加载Program时的结果。

MyClass有七个虚表槽(能够被虚分派的点子)。尽管Method一和Method二不是虚方法,它们能够在通过接口进行摊派时被认为是虚函数并进入到列表中。把.cctor和.ctor出席到列表中,你会博得总共12个艺术。最终列出的是类的三个静态域。MyClass未有实例域。其余域不言自明。

图片 25
Conclusion结论

  (三) 加载并起头化 CLTucson。

结论

大家关于CLTiggo1些最关键的内在的探赜索隐旅程终于终止了。显著,还有好多标题亟需涉及,而且必要在更加深的层次上谈论,可是大家期待那足以辅助你看来事物如何是好事。那里提供的诸多的音讯恐怕会在.NET框架和CL奥迪Q5的新生版本中改变,可是即使本文提到的CL奇骏数据结构恐怕变动,概念应该保障不变。

乘机通用语言运营时(CL安德拉)即将成为在Windows®下开发应用程序的首要选拔架构,对其展开深刻通晓会协助你建立卓有成效的工业强度的应用程序。在本文中,我们将探索CLXC90内部,包蕴对象实例布局,方法表布局,方法分派,基于接口的分摊和分化的数据结构。

  在 CL冠道 被起先化之后,在 PE 文件的 CL奥迪Q5头中就足以找到程序集的入口点(Main())。然后,JIT
初步编译并施行入口点。

我们将使用C#编纂的总结代码示例,以便任何固有的语言语法含义是C#的缺省定义。有些此处商讨的数据结构和算法大概会在Microsoft®
.NET Framework 2.0中改变,可是关键概念应该保险不变。大家采纳Visual
Studio® .NET 200三调节和测试器和调节和测试器扩大Son of Strike
(SOS)来查看本文商量的数据结构。SOS精晓CLTiguan的里边数据结构并出口有用音信。请参考“Son
of Strike”补充资料,通晓怎么将SOS.dll装入Visual Studio .NET
2003调节和测试器的进度空间。本文中,大家将讲述在共享源代码CLI(Shared Source
CLI,SSCLI)中有照应达成的类,你能够从msdn.microsoft.com/net/sscli下载。图1将帮衬您在SSCLI的数以兆计的代码中找到所参考的结构。

  综上所述,.NET
程序集的加载步骤如下:

在大家初阶前,请留心:本文提供的消息只对在X八陆平台上运转的.NET Framework
壹.壹卓有成效(对于Shared Source CLI
壹.0也多数适用,只是在好几交互操作的景况下必须小心例外),对于.NET
Framework
贰.0会有转移,所以请不要在构建软件时依赖于这一个内部结构的不变性。

  (1) 执行一个 .NET 程序集。

CLBMWX伍运转程序(Bootstrap)成立的域

在CLRubicon执行托管代码的率先行代码前,会创立几个利用程序域。当中两个对于托管代码甚至CL冠道宿主程序(CLR
hosts)都以不可知的。它们只好由CL奇骏运行进度创制,而提供CLHighlander运行进度的是shim——mscoree.dll和mscorwks.dll
(在多处理器系统下是mscorsvr.dll)。正如图2所示,那些域是系统域(System
Domain)和共享域(Shared
Domain),都以使用了单件(Singleton)情势。第伍个域是缺省应用程序域(Default
AppDomain),它是贰个AppDomain的实例,也是唯一的有命名的域。对于简易的CL纳瓦拉宿主程序,比如控制台程序,私下认可的域名由可进行映象文件的名字组成。此外的域能够在托管代码中运用AppDomain.CreateDomain方法创制,也许在非托管的代码中央银行使ICO景逸SUVRuntimeHost接口创制。复杂的宿主程序,比如ASP.NET,对于特定的网站会依照应用程序的数码创立四个域。

图片 26

2 由CLLAND运行程序创设的域

图片 27重回页首

  (二) Windows
加载器查看 AddressOfEntryPoint 域,并找到 PE 文件中的.text 段。

系统域(System Domain)

系统域负责创设和早先化共享域和暗中同意使用程序域。它将系统库mscorlib.dll载入共享域,并且珍视进程范围之中采纳的蕴藏只怕显式字符串符号。

字符串驻留(string interning)是.NET Framework
一.第11中学的多少个优化性子,它的拍卖方法显得略微昏头转向,因为CL凯雷德未有给程序集机会选用此性情。即使如此,由于在具有的行使程序域中对一个一定的标记只保留2个相应的字符串,此脾气能够节约内部存储器空间。

系统域还负责发生进度范围的接口ID,并用来创制每一种应用程序域的接口虚表映射图(InterfaceVtableMaps)的接口。系统域在经过中保持跟踪全数域,并完结加载和卸载应用程序域的效果。

图片 28再次回到页首

  (三) 位于 AddressOfEntryPoint
地点上的字节是二个 JMP 指令,用于跳转到
mscoree.dll 中的二个导入函数。

共享域(Shared Domain)

享有不属于别的特定域的代码被加载到系统库SharedDomain.Mscorlib,对于有着应用程序域的用户代码都以必需的。它会被活动加载到共享域中。系统命名空间的骨干类型,如Object,
ValueType, Array, Enum, String, and
Delegate等等,在CLEnclave运营程序进度中被事先加载到本域中。用户代码也足以被加载到那个域中,方法是在调用CorBindToRuntimeEx时利用由CLRubicon宿主程序钦定的LoaderOptimization特性。控制台程序也得以加载代码到共享域中,方法是行使System.LoaderOptimizationAttribute特性表明Main方法。共享域还管理二个选择基地址作为目录的先后集映射图,此映射图作为管理共享程序集重视关系的查找表,那么些程序集被加载到默许域(DefaultDomain)和其他在托管代码中创设的选用程序域。非共享的用户代码被加载到私下认可域。

图片 29回到页首

  (肆) 将推行控制转移到
mscoree.dll 中的函数 _CorExeMain 中,那些函数将起动 CLEscort并把推行控制转移到程序集的入口点。

默认域(Default Domain)

私下认可域是选择程序域(AppDomain)的二个实例,壹般的应用程序代码在里边运转。固然某个应用程序需求在运维时创建额外的采纳程序域(比如某些使用插件,plug-in,架构也许实行主要的运营时期码生成工作的应用程序),当先五分之三的应用程序在运维时期只开创一个域。全数在此域运维的代码都以在域层次上有上下文限制。倘诺二个应用程序有七个使用程序域,任何的域间访问会通过.NET
Remoting代理。额外的域内上下文限制消息能够动用System.ContextBoundObject派生的品类成立。种种应用程序域有协调的安全描述符(SecurityDescriptor),安全上下文(SecurityContext)和暗许上下文(DefaultContext),还有温馨的加载器堆(高频堆,低频堆和代办堆),句柄表,接口虚表管理器和程序集缓存。

图片 30回到页首

   注意,在 Windows XP
及然后版本中,对加载器举办了优化,使其能够辨识出三个 PE 文件,是不是是.NET
程序集。那样,在加载八个.NET 程序集时,就不再需求通过存根函数调用
mscoree.dll的导入函数了,而是成为自动加载 CL揽胜极光。

加载器堆(Loader Heaps)

加载器堆的功效是加载不一样的周转时CL奥迪Q5部件和优化在域的整个生命期内部存款和储蓄器在的预制构件。那个堆的提升基于可预测块,那样能够使碎片最小化。加载器堆分歧于垃圾回收堆(大概对称多处理器上的多个堆),垃圾回收堆保存对象实例,而加载器堆同时保留类型系统。平时访问的构件如方法表,方法描述,域描述和接口图,分配在数次堆上,而较少访问的数据结构如EEClass和类加载器及其查找表,分配在低频堆。代理堆保存用于代码访问安全性(code
access security, CAS)的代理部件,如COM封装调用和平台调用(P/Invoke)。

从高层次精通域后,大家准备看看它们在二个简单的应用程序的上下文中的物理细节,见图3。大家在程序运转时停在mc.Method一(),然后采纳SOS调节和测试器扩大命令DumpDomain来输出域的新闻。(请查看Son
of
Strike
刺探SOS的加载音讯)。那里是编辑后的输出:

!DumpDomain
System Domain: 793e9d58, LowFrequencyHeap: 793e9dbc,
HighFrequencyHeap: 793e9e14, StubHeap: 793e9e6c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40
Shared Domain: 793eb278, LowFrequencyHeap: 793eb2dc,
HighFrequencyHeap: 793eb334, StubHeap: 793eb38c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40
Domain 1: 149100, LowFrequencyHeap: 00149164,
HighFrequencyHeap: 001491bc, StubHeap: 00149214,
Name: Sample1.exe, Assembly: 00164938 [Sample1],
ClassLoader: 00164a78

作者们的控制台程序,萨姆ple1.exe,被加载到多个名称叫“萨姆ple1.exe”的施用程序域。Mscorlib.dll被加载到共享域,然则因为它是着力系统库,所以也在系统域中列出。每一种域会分配3个再三堆,低频堆和代办堆。系统域和共享域使用同样的类加载器,而默许应用程序使用自身的类加载器。

出口未有出示加载器堆的保留尺寸和已提交尺寸。高频堆的起头化大小是32KB,每一次提交4KB。SOS的出口也尚无体现接口虚表堆(InterfaceVtableMap)。每种域有贰个接口虚表堆(简称为IVMap),由友好的加载器堆在域初始化阶段创制。IVMap保留大小是4KB,开首时交由4KB。大家将会在一而再部分探究项目布局时探究IVMap的含义。

图2展现暗中认可的经过堆,JIT代码堆,GC堆(用于小指标)和大指标堆(用于大小相等依然超过85000字节的对象),它表达了那么些堆和加载器堆的语义不相同。即时(just-in-time,
JIT)编译器发生x8陆指令并且保留到JIT代码堆中。GC堆和大指标堆是用于托管对象实例化的污源回收堆。

图片 31回来页首

二 应用程序域

品类原理

项目是.NET编制程序中的基本单元。在C#中,类型能够选择class,struct和interface关键字展开宣示。大部分门类由程序员显式成立,但是,在专门的竞相操作(interop)情状和远程对象调用(.NET
Remoting)场地中,.NET
CLHummerH二会隐式的发生类型,这几个产生的档次涵盖COM和平运动转时可调用封装及传输代理(Runtime
Callable Wrappers and Transparent Proxies)。

大家经过三个分包对象引用的栈初步研讨.NET类型原理(典型地,栈是三个对象实例开始生命期的地方)。图4中展现的代码包涵2个简约的顺序,它有二个控制台的入口点,调用了3个静态方法。Method一创建三个SmallClass的项目实例,该类型涵盖三个字节数组,用于演示怎样在大指标堆创造对象。就算那是1段无聊的代码,可是能够协理大家开始展览座谈。

图5显示了截至在Create方法“return
smallObj;”代码行断点时的fastcall栈结构(fastcall时.NET的调用规范,它阐明在只怕的气象下将函数参数通过寄存器传递,而任何参数依据从右到左的依次入栈,然后由被调用函数完结出栈操作)。本地值类型变量objSize内含在栈结构中。引用类型变量如smallObj以固定大小(肆字节DWO牧马人D)保存在栈中,包括了在1般GC堆中分红的靶子的地方。对于守旧C++,那是目的的指针;在托管世界中,它是目的的引用。不管怎么着,它包含了1个指标实例的地方,大家将选拔术语对象实例(ObjectInstance)描述对象引用指向地址地方的数据结构。

图片 32

图5 SimpleProgram的栈结构和堆

貌似GC堆上的smallObj对象实例包罗贰个名字为_largeObj的字节数组(注意,图中显得的分寸为8501⑥字节,是实在的储备大小)。CLXC60对超越或等于85000字节的对象的拍卖和小目的分裂。大目的在大目的堆(LOH)上分红,而小目的在1般GC堆上创设,那样能够优化对象的分配和回收。LOH不会收缩,而GC堆在GC回收时展开削减。还有,LOH只会在完全GC回收时被回收。

smallObj的靶子实例包罗类型句柄(TypeHandle),指向对应项目标方法表。每一个申明的连串有一个方法表,而同等档次的兼具指标实例都针对同多个方法表。它涵盖了种类的特点新闻(接口,抽象类,具体类,COM封装和代办),达成的接口数目,用于接口分派的接口图,方法表的槽(slot)数目,指向相应达成的槽表。

措施表指向一个名叫EEClass的机要数据结构。在格局表创造前,CLQashqai类加载器从元数据中创制EEClass。图4中,SmallClass的措施表指向它的EEClass。这么些协会指向它们的模块和次序集。方法表和EEClass一般分配在共享域的加载器堆。加载器堆和选取程序域关联,那里涉及的数据结构壹旦被加载到里面,就直到应用程序域卸载时才会流失。而且,暗中同意的利用程序域不会被卸载,所以这几个代码的生存期是结束CLPAJERO关闭停止。

图片 33归来页首

  Windows 使用进程来隔绝应用程序,.NET
在此基础上进一步引人了另一种逻辑隔断层,即选择程序域。构造和管理进程的开发是这一个高的,应用程序域不小地下降在创造与销毁隔开层时所需的费用。

指标实例

正如大家说过的,全数值类型的实例也许隐含在线程栈上,恐怕隐含在GC堆上。全体的引用类型在GC堆也许LOH上开创。图6展现了1个典型的目的布局。1个指标可以通过以下途径被引用:基于栈的一部分变量,在相互操作还是平台调用情形下的句柄表,寄存器(执行格局时的this指针和情势参数),拥有终结器(finalizer)方法的对象的终结器队列。OBJECTREF不是指向目的实例的上马地点,而是有三个DWO瑞鹰D的偏移量(四字节)。此DWOQashqaiD称为对象头,保存3个指向SyncTableEntry表的目录(从一始发计数的syncblk编号。因为经过索引举行几次三番,所以在急需扩展表的轻重缓急时,CLCRUISER能够在内部存款和储蓄器中活动那些表。SyncTableEntry维护三个反向的弱引用,以便CLPRADO能够跟踪SyncBlock的全体权。弱引用让GC能够在并未其它强引用存在时回收对象。SyncTableEntry还保存了1个指向SyncBlock的指针,包括了很少需求被四个指标的兼具实例使用的有用的音讯。那个信息包蕴对象锁,哈希编码,任何转换层(thunking)数据和动用程序域的目录。对于绝大部分的对象实例,不会为实际的SyncBlock分配内部存款和储蓄器,而且syncblk编号为0。这点在实施线程遇到如lock(obj)可能obj.GetHashCode的言语时会爆发变化,如下所示:

SmallClass obj = new SmallClass()
// Do some work here
lock(obj) { /* Do some synchronized work here */ }
obj.GetHashCode();

在以上代码中,smallObj会使用0作为它的苗子的syncblk编号。lock语句使得CL大切诺基创设一个syncblk入口并行使相应的数值更新对象头。因为C#的lock关键字会扩充为try-finally语句并利用Monitor类,贰个看作同步的Monitor对象在syncblk上创造。堆GetHashCode的调用会选拔对象的哈希编码增添syncblk。

在SyncBlock中有别的的域,它们在COM交互操作和封送委托(marshaling
delegates)到非托管代码时使用,然而那和杰出的对象用处无关。

花色句柄紧跟在指标实例中的syncblk编号后。为了保持接二连三性,作者会在证实实例变量后钻探类型句柄。实例域(Instance
田野(field))的变量列表紧跟在档次句柄后。暗许情状下,实例域会以内部存款和储蓄器最有效行使的点子排列,那样只供给最少的作为对齐的填充字节。图7的代码展现了SimpleClass包涵有局地例外尺寸的实例变量。

图8显示了在Visual
Studio调节和测试器的内存窗口中的1个SimpleClass对象实例。我们在图7的return语句处设置了断点,然后使用ECX寄存器保存的simpleObj地址在内部存款和储蓄器窗口显示对象实例。前五个字节是syncblk编号。因为大家从不用其余共同代码应用此实例(也从没访问它的哈希编码),syncblk编号为0。保存在栈变量的目的实例,指向起首地方的五个字节的偏移处。字节变量b一,b②,b三和b4被一个接3个的排列在一起。四个short类型变量s1和s二也被排列在一齐。字符串变量str是2个四字节的OBJECTREF,指向GC堆中抽成的骨子里的字符串实例。字符串是三个尤其的品类,因为兼具包涵同样文字标记的字符串,会在程序集加载到进度时指向1个大局字符串表的同样实例。那一个历程称为字符串驻留(string
interning),设计指标是优化内部存款和储蓄器的接纳。我们事先曾经提过,在NET Framework
1.1中,程序集不可能选择是不是利用那么些进度,即使以后版本的CL揽胜极光可能会提供这么的力量。

之所以暗中认可意况下,成员变量在源代码中的词典顺序未有在内部存款和储蓄器中保持。在相互操作的场所下,词典顺序必须被保留到内部存款和储蓄器中,那时能够应用StructLayoutAttribute性情,它有三个LayoutKind的枚举类型作为参数。LayoutKind.Sequential能够为被封送(marshaled)数据保持词典顺序,固然在.NET
Framework 1.第11中学,它未有影响托管的布局(不过.NET Framework
二.0大概会这么做)。在交互操作的情况下,要是您真的要求非凡的填充字节和体现的控制域的依次,LayoutKind.Explicit能够和域层次的FieldOffset天性一起行使。

看完底层的内部存款和储蓄器内容后,大家使用SOS看看对象实例。1个灵光的一声令下是DumpHeap,它能够列出全部的堆内容和二个特别类型的具有实例。无需依靠寄存器,DumpHeap能够显示大家创制的绝无仅有二个实例的地点。

!DumpHeap -type SimpleClass
Loaded Son of Strike data table version 5 from
"C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"
Address       MT     Size
00a8197c 00955124       36
Last good object: 00a819a0
total 1 objects
Statistics:
MT    Count TotalSize Class Name
955124        1        36 SimpleClass

对象的总大小是3陆字节,不管字符串多大,SimpleClass的实例只包蕴2个DWOHavalD的对象引用。SimpleClass的实例变量只占用2八字节,别的7个字节包蕴项目句柄(4字节)和syncblk编号(4字节)。找到simpleObj实例的地方后,我们得以使用DumpObj命令输出它的剧情,如下所示:

!DumpObj 0x00a8197c
Name: SimpleClass
MethodTable 0x00955124
EEClass 0x02ca33b0
Size 36(0x24) bytes
FieldDesc*: 00955064
MT    Field   Offset                 Type       Attr    Value Name
00955124  400000a        4         System.Int64   instance      31 l1
00955124  400000b        c                CLASS   instance 00a819a0 str
<< some fields omitted from the display for brevity >>
00955124  4000003       1e          System.Byte   instance        3 b3
00955124  4000004       1f          System.Byte   instance        4 b4

正如此前说过,C#编写翻译器对于类的默许布局使用LayoutType.Auto(对于协会接纳LayoutType.Sequential);由此类加载器重新排列实例域以最小化填充字节。大家能够利用ObjSize来输出包罗被str实例占用的空间,如下所示:

!ObjSize 0x00a8197c
sizeof(00a8197c) =       72 (    0x48) bytes (SimpleClass)

借使你从目的图的全局大小(7二字节)减去SimpleClass的尺寸(36字节),就能够获得str的深浅,即36字节。让我们输出str实例来表达这么些结果:

!DumpObj 0x00a819a0
Name: System.String
MethodTable 0x009742d8
EEClass 0x02c4c6c4
Size 36(0x24) bytes

万一您将字符串实例的分寸(3六字节)加上SimpleClass实例的分寸(3陆字节),就足以获得ObjSize命令报告的总大小72字节。

请小心ObjSize不含有syncblk结构占用的内部存款和储蓄器。而且,在.NET Framework
一.第11中学,CLPRADO不知晓非托管能源占用的内部存款和储蓄器,如GDI对象,COM对象,文件句柄等等;因而它们不会被这么些命令报告。

针对方法表的项目句柄在syncblk编号后分配。在指标实例创建前,CLEscort查看加载类型,如若未有找到,则展开加载,得到方法表地址,创设对象实例,然后把项目句柄值追加到对象实例中。JIT编写翻译器产生的代码在实行艺术分派时利用项目句柄来恒定方法表。CL奥迪Q7在急需史能够因而措施表反向访问加载类型时行使项目句柄。

图片 34回来页首

  进程与行使程序域的关系如下:

方法表

每种类和实例在加载到使用程序域时,会在内部存款和储蓄器中经过艺术表来表示。那是在目的的率先个实例创制前的类加载活动的结果。对象实例表示的是情状,而艺术表表示了作为。通过EEClass,方法表把对象实例绑定到被语言编写翻译器产生的炫耀到内部存款和储蓄器的元数据结构(metadata
structures)。方法表包罗的音讯和外挂的新闻方可经过System.Type访问。指向方法表的指针在托管代码中得以由此Type.RuntimeTypeHandle属性得到。对象实例包括的连串句柄指向方法表发轫地点的晃动处,偏移量私下认可情形下是1二字节,包罗了GC音讯。我们不打算在那里对其展开座谈。

图9显示了措施表的独领风流布局。我们会注解项目句柄的局地首要的域,不过对于截然的列表,请参考此图。让我们从基实例大小(Base
Instance Size)先导,因为它间接关系到运营时的内存状态。

图片 35重返页首

图片 36

基实例大小

基实例大小是由类加载器计算的对象的大大小小,基于代码中宣称的域。从前曾经钻探过,当前GC的兑现内需1个起码1贰字节的指标实例。假设二个类没有概念任何实例域,它至少含有额外的五个字节。别的的九个字节被对象头(大概带有syncblk编号)和种类句柄占用。再说3次,对象的轻重会惨遭StructLayoutAttribute的熏陶。

看看图3中显示的MyClass(有两个接口)的办法表的内存快速照相(Visual
Studio .NET
200三内部存款和储蓄器窗口),将它和SOS的出口实行相比。在图9中,对象大小位于肆字节的偏移处,值为1二(0x0000000C)字节。以下是SOS的DumpHeap命令的输出:

!DumpHeap -type MyClass
Address       MT     Size
00a819ac 009552a0       12
total 1 objects
Statistics:
MT  Count TotalSize Class Name
9552a0      1        12    MyClass

图片 37回来页首

  在其余运营了 CL奥迪Q7 的 Windows
进度中都会定义二个或八个使用程序域,在这几个域中包罗了可实施代码、数据、元数据结构以及财富等。除了进度自己的护卫机制外,应用程序域还特别引人了以下爱慕体制:

办法槽表(Method Slot Table)

在艺术表中含有了2个槽表,指向各种艺术的描述(MethodDesc),提供了品种的行为能力。方法槽表是依照方法完成的线性链表,依据如下顺序排列:继承的虚方法,引进的虚方法,实例方法,静态方法。

类加载器在现阶段类,父类和接口的元数据中遍历,然后创设方法表。在排列进度中,它替换全体的被覆盖的虚方法和被埋伏的父类方法,成立新的槽,在急需时复制槽。槽复制是不可缺少的,它能够让每种接口有和好的微乎其微的vtable。可是被复制的槽指向同壹的情理达成。MyClass包蕴接口方法,二个类构造函数(.cctor)和对象构造函数(.ctor)。对象构造函数由C#编写翻译器为具备未有显式定义构造函数的指标自动生成。因为大家定义并起先化了多少个静态变量,编写翻译器会扭转一个类构造函数。图10来得了MyClass的诀窍表的布局。布局突显了11个办法,因为Method二槽为接口IVMap实行了复制,下边大家会开始展览商讨。图11呈现了MyClass的方法表的SOS的出口。

别的项目标开首五个办法总是ToString, Equals, GetHashCode, and
Finalize。那么些是从System.Object继承的虚方法。Method2槽被开始展览了复制,然则都指向相同的措施描述。代码突显定义的.cctor和.ctor会分别和静态方法和实例方法分在壹组。

图片 38回去页首

  • 一个采取程序域中的错误代码不会潜移默化到同3个进程中另1个施用程序域中运营的代码。
  • 三个采用程序域中的代码不能直接访问另一个用到程序域中的财富。
  • 各个应用程序域中都能够配备与代码特定的音信,如安全设置。

措施描述(MethodDesc)

方法描述(MethodDesc)是CL奥迪Q5知道的艺术达成的1个封装。有几体系型的格局描述,除了用于托管完毕,分别用于差别的相互操作完毕的调用。在本文中,大家只侦察图3代码中的托管方法描述。方法描述在类加载过程中发出,开头化为指向IL。每个方法描述包罗二个预编译代理(PreJitStub),负责触发JIT编写翻译。图12出示了3个压倒一切的布局,方法表的槽实际上指向代理,而不是实际上的秘籍描述数据结构。对于实际的章程描述,这是-5字节的撼动,是各类方法的七个叠加字节的一片段。那5个字节包涵了调用预编译代理程序的一声令下。五字节的偏移能够从SOS的DumpMT输出从察看,因为方法描述总是方法槽表指向的职位前面包车型大巴多少个字节。在首先次调用时,会调用JIT编写翻译程序。在编写翻译完结后,包蕴调用指令的四个字节会被跳转到JIT编写翻译后的x8六代码的无偿跳转指令覆盖。

图片 39

图12 方法描述

对图1②的办法表槽指向的代码举办反汇编,显示了对预编写翻译代理的调用。以下是在Method二被JIT编写翻译前的反汇编的简化展现。

!u 0x00955263
Unmanaged code
00955263 call        003C3538        ;call to the jitted Method2()
00955268 add         eax,68040000h   ;ignore this and the rest
;as !u thinks it as code

今昔我们进行此措施,然后反汇编相同的位置:

!u 0x00955263
Unmanaged code
00955263 jmp     02C633E8        ;call to the jitted Method2()
00955268 add     eax,0E8040000h  ;ignore this and the rest
;as !u thinks it as code

在此地方,只有伊始三个字节是代码,剩余字节包涵了Method二的法子描述的数额。“!u”命令不通晓那或多或少,所以生成的是无规律的代码,你能够忽略伍个字节后的保有东西。

CodeOrIL在JIT编译前蕴涵IL中艺术完毕的争持虚地址(Relative Virtual
Address
,QashqaiVA)。此域用作标志,表示是还是不是IL。在按需要编写翻译后,CL路虎极光使用编写翻译后的代码地址更新此域。让大家从列出的函数中精选3个,然后用DumpMT命令分别出口在JIT编写翻译前后的艺术描述的剧情:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
IL RVA : 00002068

编写翻译后,方法描述的始末如下:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
Method VA : 02c633e8

艺术的这一个标志域的编码包涵了主意的项目,例如静态,实例,接口方法大概COM达成。让大家看方法表别的二个复杂的地方:接口完结。它包裹了布局过程具有的错综复杂,让托管环境觉得这点看起来不难。然后,大家将表明接口怎么样开展示公布局和依照接口的点子分派的适度工作办法。

图片 40回到页首

  对于从未显式创设应用程序域的应用程序来说,CL宝马7系会创制五个利用程序域:系统利用程序域、共享利用程序域、私下认可使用程序域。

接口虚表图和接口图

在艺术表的第1二字节偏移处是叁个不可或缺的指针,接口虚表(IVMap)。如图9所示,接口虚表指向四个选拔程序域层次的映射表,该表以进程层次的接口ID作为目录。接口ID在接口类型第3次加载时创制。每一个接口的贯彻都在接口虚表中有一个笔录。借使MyInterface1被三个类完结,在接口虚表表中就有七个记录。该记录会反向指向MyClass方法表内含的子表的开始地方,如图9所示。这是接口方法分派产生时选用的引用。接口虚表是依照方法表内含的接口图消息创造,接口图在章程表布局进度中基于类的元数据创立。一旦类型加载成功,唯有接口虚表用于方法分派。

第三八字节地方的接口图会指向内含在艺术表中的接口音信记录。在那种气象下,对MyClass达成的多少个接口中的每三个都有两条记下。第三条接口音信记录的上马5个字节指向MyInterface一的项目句柄(见图9和图10)。接着的WOLANDD(二字节)被1个申明占用(0表示从父类派生,一象征由近来类达成)。在评释后的WO卡宴D是二个上马槽(Start
Slot),被类加载器用来布局接口完毕的子表。对于MyInterface二,开首槽的值为四(从0初阶编号),所以槽5和陆指向完结;对于MyInterface二,开头槽的值为6,所以槽柒和捌指向完结。类加载器会在急需时复制槽来发生这么的功效:各个接口有温馨的落到实处,然则物理映射到同样的点子描述。在MyClass中,MyInterface壹.Method2和MyInterface二.Method二会指向相同的兑现。

根据接口的法子分派通过接口虚表进行,而向来的办法分派通过保留在1一槽的点子描述地址进行。如从前聊到,.NET框架使用fastcall的调用约定,起始三个参数在也许的时候1般通过ECX和EDX寄存器传递。实例方法的首先个参数总是this指针,所以通过ECX寄存器传送,能够在“mov
ecx,esi”语句看到那或多或少:

mi1.Method1();
mov    ecx,edi                 ;move "this" pointer into ecx
mov    eax,dword ptr [ecx]     ;move "TypeHandle" into eax
mov    eax,dword ptr [eax+0Ch] ;move IVMap address into eax at offset 12
mov    eax,dword ptr [eax+30h] ;move the ifc impl start slot into eax
call   dword ptr [eax]         ;call Method1
mc.Method1();
mov    ecx,esi                 ;move "this" pointer into ecx
cmp    dword ptr [ecx],ecx     ;compare and set flags
call   dword ptr ds:[009552D8h];directly call Method1

这一个反汇编展现了第贰手调用MyClass的实例方法未有动用偏移。JIT编写翻译器把办法描述的地方直接写到代码中。基于接口的摊派通过接口虚表爆发,和直接分派相比较必要有的相当的指令。1个指令用来赢得接口虚表的地点,另3个获取格局槽表中的接口完毕的始发槽。而且,把贰个对象实例转换为接口只供给拷贝this指针到指标的变量。在图第22中学,语句“mi一=mc”使用三个指令把mc的目的引用拷贝到mi一。

图片 41回来页首

(壹) 系统利用程序域

虚分派(Virtual Dispatch)

现行反革命大家看看虚分派,并且和基于接口的摊派举行相比。以下是图3中MyClass.Method叁的虚函数调用的反汇编代码:

mc.Method3();
Mov    ecx,esi               ;move "this" pointer into ecx
Mov    eax,dword ptr [ecx]   ;acquire the MethodTable address
Call   dword ptr [eax+44h]   ;dispatch to the method at offset 0x44

虚分派总是通过3个稳住的槽编号发生,和章程表指针在一定的类(类型)达成层次非亲非故。在格局表布局时,类加载器用覆盖的子类的落到实处代替父类的落到实处。结果,对父对象的艺术调用被分摊到子对象的兑现。反汇编呈现了分派通过捌号槽产生,能够在调节和测试器的内部存款和储蓄器窗口(如图10所示)和DumpMT的输出看到那或多或少。

图片 42回到页首

  系统运用程序域首要效率如下:

静态变量

静态变量是情势表数据结构的基本点组成都部队分。作为艺术表的壹有个别,它们分配在章程表的槽数组后。全数的原有静态类型是内联的,而对于协会和引用的花色的静态值对象,通在句柄表中创建的靶子引用来针对。方法表中的对象引用指向应用程序域的句柄表的指标引用,它引用了堆上成立的靶子实例。1旦创立后,句柄表内的对象引用会使堆上的对象实例保持生存,直到应用程序域被卸载。在图9
中,静态字符串变量str指向句柄表的指标引用,后者指向GC堆上的MyString。

图片 43回来页首

  • 始建别的八个使用程序域(共享利用程序域、暗中同意使用程序域)。

  • mscoree.dll加载到共享应用程序域中。
  • 记录进程中全数别的的运用程序域,包含提供加载、卸载应用程序域等成效。
  • 记录字符串池中的字符串常量,因而同意任意字符串在每种进度中都留存五个副本。
  • 初步化特定类型的百般。

EEClass

EEClass在格局表创设前早先生活,它和格局表组成起来,是体系证明的CLHummerH二版本。实际上,EEClass和方法表逻辑上是2个数据结构(它们1起表示贰个品种),只但是因为使用频度的例外而被分别。常常选取的域放在方法表,而不常常使用的域在EEClass中。那样,须求被JIT编译函数使用的新闻(如名字,域和摇头)在EEClass中,可是运营时索要的音讯(如虚表槽和GC音讯)在格局表中。

对每叁个品种会加载贰个EEClass到使用程序域中,包含接口,类,抽象类,数组和协会。种种EEClass是八个被实践引擎跟踪的树的节点。CLEnclave使用那个互连网在EEClass结构中浏览,其指标包罗类加载,方法表布局,类型验证和类型转换。EEClass的子-父关系基于继承层次建立,而父-子关系基于接口层次和类加载顺序的重组。在推行托管代码的进程中,新的EEClass节点被到场,节点的涉嫌被补充,新的涉嫌被确立。在互联网中,相邻的EEClass还有一个品位的关联。EEClass有多少个域用于管理被加载类型的节点关系:父类(Parent
Class),相邻链(sibling chain)和子链(children
chain)。关于图4中的MyClass上下文中的EEClass的语义,请参考图壹叁。

图13只展示了和那一个议论相关的一些域。因为大家忽略了布局中的一些域,我们并未在图中正好突显偏移。EEClass有贰个直接的对于艺术表的引用。EEClass也针对在私下认可使用程序域的屡屡堆分配的措施描述块。在点子表创立时,对进度堆上分配的域描述列表的三个引用提供了域的布局消息。EEClass在使用程序域的低频堆分配,那样操作系统能够越来越好的拓展内部存款和储蓄器分页管理,因而裁减了工作集。

图片 44

图13 EEClass 布局

图壹三中的此外域在MyClass(图3)的上下文的含义不言自明。大家明日看看使用SOS输出的EEClass的的确的物理内部存款和储蓄器。在mc.Method1代码行设置断点后,运营图3的次第。首先使用命令Name二EE获得MyClass的EEClass的地点。

!Name2EE C:\Working\test\ClrInternals\Sample1.exe MyClass
MethodTable: 009552a0
EEClass: 02ca3508
Name: MyClass

Name二EE的第3个参数时模块名,能够从DumpDomain命令得到。今后我们获得了EEClass的地址,大家输出EEClass:

!DumpClass 02ca3508
Class Name : MyClass, mdToken : 02000004, Parent Class : 02c4c3e4
ClassLoader : 00163ad8, Method Table : 009552a0, Vtable Slots : 8
Total Method Slots : a, NumInstanceFields: 0,
NumStaticFields: 2,FieldDesc*: 00955224
MT    Field   Offset  Type           Attr    Value    Name
009552a0  4000001   2c      CLASS          static 00a8198c  str
009552a0  4000002   30      System.UInt32  static aaaaaaaa  ui 

图13和DumpClass的输出看起来完全相同。元数据令牌(metadata
token,mdToken)表示了在模块PE文件中映射到内部存款和储蓄器的元数据表的MyClass索引,父类指向System.Object。从相邻链指向名叫Program的EEClass,能够精通图13浮现的是加载Program时的结果。

MyClass有7个虚表槽(能够被虚分派的主意)。固然Method壹和Method二不是虚方法,它们得以在经过接口进行摊派时被认为是虚函数并插足到列表中。把.cctor和.ctor加入到列表中,你会获得总共十三个主意。最终列出的是类的四个静态域。MyClass没有实例域。其余域不言自明。

图片 45重返页首

(2) 共享应用程序域

Conclusion结论

作者们关于CL大切诺基一些最注重的内在的追究旅程终于截止了。显著,还有好多标题亟需涉及,而且须求在越来越深的层次上探讨,不过我们期待这足以支持你见到事物怎么样行事。那里提供的众多的新闻只怕会在.NET框架和CLQashqai的新生版本中改变,可是尽管本文提到的CLLX570数据结构也许改动,概念应该保险不变。

Hanu Kommalapati是微软居尔f
Coast区(休斯顿)的一名架构师。他在微软明日的剧中人物是赞助客户基于.NET框架建立可扩充的零部件框架。可以通过hanuk@microsoft.com联系他。

Tom
Christian
是微软成本帮忙高工,使用ASP.NET和用来WinDBG的.NET调试器扩大(sos/
psscor)。他在北卡罗来州的夏洛蒂,可以透过tomchris@microsoft.com联系他。

翻译者卢克是微软集团的软件工程师,习惯使用C++和C#支付应用程序。闲暇时光他喜爱音乐,旅游和怀旧游戏,并且愿意帮助MSDN翻译更加多的小说和其他开发者共享。能够经过ecaijw@msn.com联系她。

  在共享利用程序域中含有的是与利用程序域毫无干系的代码。mscoree.dll
将被加载到那一个利用程序域中,其它还包含在 System
命名空间中的一些中央项目(eg.String、Array等)。在多数境况下,非用户代码将被加载到共享应用程序域中。启用了
CL本田CR-V 的施用程序域能够通过加载器的优化属性来注入用户代码。

(叁) 暗许应用程序域

  常常,.NET
程序在默许使用程序域中运维。位于私下认可使用程序域中的全体代码都唯有在那一个域中才是实用的。由于使用程序域达成了壹种逻辑并且可相信的界线,因而任何跨越应用程序域的拜访操作都必须通过.NET
远程对象来开始展览。

  下图呈现了本文起首创设的 德姆o
的选用程序域音信:

图片 46

三 解析类型引用

  运营应用程序时,CL哈弗会加载并开首化它。然后 CL途锐 读取程序集的 CLGL450头,查找标识了应用程序入口的不二等秘书诀(Main())的 MethodDefToken。然后,CLKoleos会搜索 MethodDef 元数据表,找到该格局的 IL 代码在文书中的偏移量,把那个IL 代码 JIT
编写翻译为地面代码。编写翻译时会对代码举办表明以管教项目安全性。最后,将推行本地代码。在
JIT 编写翻译时,CLRubicon会检核对品种和分子的持有引用,并加载定义了它们的程序集(倘若未有加载),CL奇骏必须稳定并加载程序集。解析3个引用的花色时,CL奥迪Q5大概在以下多个地方找到类型:

  • 同八个文书 
  • 今非昔比文件,相同程序集
  • 不等文件,分化程序集

  解析2个档次引用时只要发送任何错误,如找不到文件、文件不能够加载、哈希值不相配等,就会抛出越发。下图演示了项目绑定的经过:

图片 47

  (注意 ModuleDef、ModuleRef、FileDef
元数据表使用文件名及其扩大名来引用文件。而 AssemblyRef
元数据表使用不带扩大名的文书名来引用程序集。要和三个程序集绑定时,系统通过探测目录尝试定位文件。)

  对于 CLLAND来说,全数程序集都以依照名称、版本、语言文化、公钥来标识的。不过,GAC
遵照名称、版本、语言文化、公钥和 CPU 架构来标识程序集。在 GAC
中搜寻程序集时,CL冠道判断应用程当前在哪些项目的经过中运作(三十二位、6二人)。然后,CLHummerH二首先搜索程序集的那种 CPU 架构专用版本,借使未有找到,就摸索不区分 CPU
的版本。

四 类型

  类型是.NET
程序中的基本编制程序单元。在.NET
应用程序中,要么使用自定义的类型,要么选择现有的门类。类型分为两类:值类型和引用类型。值类型是指保存在线程栈上的品种,包蕴:枚举、结构以及不难类型(如
int、bool、char等)。常常,值类型是一些占据内部存款和储蓄器空间较小的档次。另壹连串型叫做引用类型,它是在堆上分配的,并由垃圾回收器(GC)负责管理。在引用类型中也足以涵盖值类型,在那种场馆下,值类型将同一位于堆上并且由垃圾收集器来管理。

  托管堆上对象的布局如下:

图片 48

  在托管堆上的各类对象实例中都含有了以下新闻:

  • 同步块(sync
    block):同步块能够是二个位掩码,也得以是由 CLGL450维持的1道块表中的索引,其中蕴蓄了关于目的自作者的提携信息。
  • 品种句柄(type handle):类型句柄是
    CL奇骏类型系统的底蕴单元,能够用来对托管堆上的品种举办总体描述。
  • 目的实例:在壹起块索引和项目句柄之后接着是实际上的指标数据。

  下图体现了 德姆o 的 Circle
对象的剧情:

图片 49

(壹) 同步块表

   在托管堆上各样对象的眼下都有多个壹块块索引,它指向
CL奥德赛中个人堆上的共同块表。在同步块表中包括的是指向各样同步块的指针,在联合署名块中富含了无数信息,如目的的锁、互用性数据、应用程序域索引、对象的散列码(hash
code)等。当然,在对象中也说不定不带有别的共同块数据,此时的联合块索引值为0。需求小心的是,在1块儿块中并不一定只含有简单的目录,也足以涵盖对象的别的帮扶消息。

  (在利用索引时要留心,CLCR-V能够自由移动/拉长同步块表,同时却不自然对拥有包涵一块块的靶子头举办调整。)

(二) 类型句柄

  引用类型的全数实例都被放在托管堆上,那么些堆是由
GC
来支配。在装有的实例中都包罗了1个连串句柄。简单地说,类型句柄指向的是某个项指标方法表。在格局表中蕴藏了各个元数据,它们完整地描述了那些项目。下图表明了点子表的完好内部存款和储蓄器布局:

图片 50

  类型句柄是 CL奥迪Q5类型系统中的粘合剂,它把对象实例及其全部的相关品种数据涉嫌起来。对象实例的类别句柄存款和储蓄在托管堆上,它是七个指南针,指向类型的方法表。在艺术表中富含了有关指标类型的汪洋音信,包蕴针对任何首要CLKuga 数据结构(如
EEClass)的指针。在项目句柄指向的首先类数据中含有了关于项目笔者的局部音讯(如标志、大小、方法数量、父方法表等)。下1个要注意的域是一个指针,指向一个EEClass。方法表的下一部分也是三个指南针,指向与类型相关的模块消息。在剩余的域中包括了类别的虚方法表。要求小心的是,在点子表中的局地措施指针大概会指向非托管代码。出现那种情景的原由是,一些情势恐怕还并未有被
JIT 编写翻译器编写翻译。事实上,运维编写翻译进程的 JIT
存根代码是1段非托管代码,当方法没有被 JIT
编写翻译器编写翻译时,它会指向那段非托管代码,在编写翻译之后会把执行控制权转移到新编译生成的代码。

(3) 方法描述符

  在点子表中富含了虚方法表,里面富含了有个别针对性隐藏在档次方法背后的代码的指针。虚方法表中包涵了指向代码的指针,那么些艺术自己能够自行描述,那都归功于艺术描述符。在点子描述符中包括了关于艺术的详细音信,如方法的文件表示、它所在的模块、标记以及落到实处格局的代码地址。

  下图展现了 Demo 的 Circle
对象的方法表及艺术描述符:

图片 51

  查看 GetCircumference 方法的
IL:

图片 52

  进一步取得格局的消息:

图片 53

(四) 模块

  查看类型 Circle
所在模块的音信:

图片 54

(5) 元数据符号

  CLQX56的元数据以表格的款式储存在运维时引擎中,元数据符号是一个四字节的值,其布局如下:

图片 55

  查看 Circle
的秘籍表能够看出元数据符号:

图片 56

  值为 0三千00四的元数据符号能够表达为:指向类型定义表中的第四个目录。

(六)EEClass

  EEClass
数据结构能够看作是艺术表的1个逻辑等价物,因而它可以看做落实 CL陆风X8类型系统自描述性的壹种机制。本质上,EEClass
和措施表是三种截然区别的结构,但从逻辑来看,它们都表示同样的定义。之所以分成那两种数据结构,是因为
CLLacrosse使用类型域的往往程度不一。频仍使用的域被保存到方法表中,而不频仍利用的域被保存到
EEClass 中。EEClass 的大体结构如下:

图片 57

  C# 中的层次结构在 EEClass
中壹律适用。当 CL奥迪Q五 加载类型时,会创造3个类别的 EEClass
节点层次结构,在那之中富含了指向父节点和兄弟节点的指针,那样就能够遍历整个层次结构。EEClass
中的方法描述块域,包罗了三个指南针,指向类型中的第二组方法描述符,那样就能遍历任意类型中的方法描述符。在每组方法描述符中又富含指向链表中下1组方法描述符的指针。

  查看 Circle 的 EEClass:

图片 58

5 内部存款和储蓄器分配

  CLTiggo管理的内部存款和储蓄器首要分为三部分,如下:

  • 线程栈
    用于分配值类型实例。线程栈主要由操作系统一管理理,而不受垃圾收集器的主宰,当班值日类型实例所在艺术甘休时,其储存单位活动释放。栈的施行功效高,但存款和储蓄体积有限。
  • 小型对象堆(SOH) 用于分配小目的实例。若是引用类型对象的实例大小小于85000字节,实例将被分配在SOH堆上,当有内部存款和储蓄器分配依旧回收时,垃圾收集器恐怕会对SOH堆举行压缩。
  • 特大型对象堆(LOH) 用以分配大目的实例。假如引用类型对象的实例大小十分大于八五千字节时,该实例将被分配到LOH堆上,分歧于SOH堆,垃圾收集器不会对LOH堆进行压缩。

六类型、对象、线程栈、托管堆在运行时的交互交换

  运转 德姆o
时,会运转3个历程,因为程序本身是单线程的拥有唯有三个线程。2个线程被创制时会分配到
1MB
大小的栈。那一个栈的上空用于向方法传递实参,并用以方法内部定义的部分变量。

  今后,Windows 进度早已起步,CL凯雷德已经加载到当中,托管堆已起始化,而且已创设三个线程(连同它的 1MB
栈空间)。以后早就进来 Main() 方法,立即快要执行 Main
中的语句,所以栈和堆的情况如下图所示(为了简化示意图,作者只画出了自定义的花色):

图片 59

  当 JIT 编写翻译器将 Main() 方法的 IL
代码转换费用地 CPU 指令时,会专注到其内部引用的具有类型。这年,CLRubicon要力佛山义了这几个项目标享有程序集都已加载。然后接纳程序集的元数据,CL福睿斯提取与这个类别有关的新闻,并创建1些数据结构来代表项目小编。在线程执行本地代码前,会成立所需的有着目的。下图呈现了在
Main 被调用时,创设项目对象后的情形:

图片 60

  当 CLXC90明确方法必要的装有品类对象都已创设,而且 Main
的代码已经编写翻译之后,就同意线程开端实践编写翻译好的本地代码。首先实施的是
“Circle circle = new Circle(四.0);”,那会创建二个 Circle
类型的壹对变量,并为其赋值。当调用构造函数时,会在托管堆中开创 Circle
的实例。任曾几何时候在堆上新建2个指标 CL福睿斯都会自行开始化内部类型对象指针成员,将它引用与指标对应的体系对象。别的,CL福睿斯会先伊始化同步块索引,将目的的有着实例字段设置为 null 或
0,再调用类型的构造器。new 操作符会再次回到 Circle
对象的内部存款和储蓄器地址,该地点将保留在1些变量 circle
中(在线程栈上)。此时的事态如下图:

图片 61

  接着执行“Console.WriteLine(circle.ToString());”。ToString()
方法是叁个虚方法,在调用虚方法时,JIT
编写翻译器要在章程中生成1些相当的代码,方法每回调用时都会履行那一个代码。那个代码首先检查发出调用的变量,然后跟随处址来到爆发调用的靶子。在本例中,变量
circle 引用的是 Circle
类型的多个指标。然后,代码检核查象内部的“类型句柄”成员,那个成员指向对象的莫过于类型。然后,代码在类型对象的措施表中搜索引用了被调用方法的记录项,对艺术开展JIT 编写翻译(假如要求),再调用 JIT 编写翻译过的代码。就本例来说,调用的是
Circle 类型的 ToString 完结。(在调用非虚方法时,JIT
编写翻译器会找到调用对象的档次对应的档次对象。假诺该品种未有定义非凡格局,JIT
编写翻译器就会想起类层次结构,从来回溯到
Object,并在沿途的各类体系中查找该方法。)

  WriteLine(string)
是静态方法。调用四个静态方法时,CL牧马人会定位与静态方法的类型对应的类型对象。然后,JIT
编译器在档次对象的法门表中搜索与被调用的诀窍对应的记录项,对艺术开始展览 JIT
编写翻译(假诺必要),再调用 JIT
编写翻译的代码。综上所述,“Console.WriteLine(circle.ToString());”的操作结果如下图所示:

图片 62

  最终,执行“Console.ReadKey();”,与WriteLine(string)
类似,那里就不再赘言。大家能够见见,Circle
类型对象也含有“类型句柄”成员。那是因为项目对象本质上也是指标。CL锐界成立项目对象时,必须初步化那些分子。CL库罗德 起头在多个经过中运营时,会立马为 mscorlib.dll 中定义的 System.Type
类型创立3个卓越的花色对象。Circle
类型对象是该品种的实例。因而,在开头化时,Circle
类型对象的种类句柄会开首化为对 System.Type
类型对象的引用。如下图所示:

图片 63

  System.Type
类型对象自笔者也是二个指标,内部的档次句柄指向它本人。System.Object 的
GetType 方法重回的是储存在钦定对象的体系句柄(是三个指针)。

相关文章