原来的书文地址:http://msdn.microsoft.com/en-us/magazine/cc163791.aspx
原著宣布日期: 9/19/二〇〇七
初稿已经被 Microsoft
删除了,搜聚进度中发现大多篇章图都不全,那是因为原来的小说的图都不全,所以特搜聚完整全文。

目录

目录

一 加载.NET
程序集

前言

  • SystemDomain, SharedDomain, and DefaultDomain。
  • 目标布局和内部存款和储蓄器细节。
  • 措施表布局。
  • 方式分派(Method dispatching)。

因为国有语言运转时(CL传祺)将在成为在Windows上创建应用程序的主演级基础架构,
多精通点关于CLSportage的纵深认知会帮忙您营造高速的, 工业级健壮的施用程序.
在那篇小说中, 大家会浏览,考察CLRAV4的内在精神, 饱含对象实例布局,
方法表的布局, 方法分派, 基于接口的摊派, 和形形色色的数据结构.

大家会选用由C#写成的特别轻易的代码示例,
所以任何对编制程序语言的隐式引用都以以C#语言为对象的.
研讨的一些数据结构和算法会在Microsoft® .NET Framework 2.0中更动,
但是大多的定义是不会变的. 大家会接纳Visual Studio® .NET 二零零二Debugger和debugger extension Son of Strike (SOS)来窥伺者一些数量结构.
SOS能够清楚CL福特Explorer内部的数据结构, 可以dump出有用的音信. 通篇,
我们商谈谈在Shared Source CLI(SSCLI)中兼有相关落到实处的类, 你能够从
http://msdn.microsoft.com/net/sscli 下载到它们.

图表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

在大家初步前,请留意:本文提供的音信只对在X86平台上运维的.NET Framework
1.1使得(对于Shared Source CLI
1.0可能多适用,只是在有个别交互操作的情事下必需注意例外),对于.NET
Framework
2.0会有变动,所以请不要在创设软件时信赖于那几个内部结构的不改变性。


应用程序域

CL中华V运维程序(Bootstrap)创建的域

在CLPAJERO试行托管代码的第一行代码前,会成立多少个使用程序域。当中多个对于托管代码乃至CLQX56宿主程序(CLR
hosts)都是不可知的。它们只好由CLPRADO运维进程成立,而提供CLEvoque运营进度的是shim——mscoree.dll和mscorwks.dll
(在多管理器系统下是mscorsvr.dll)。正如 图2
所示,这一个域是系统域(System Domain)和分享域(Shared
Domain),都是选取了单件(Singleton)形式。第七个域是缺省应用程序域(Default
AppDomain),它是三个AppDomain的实例,也是并世无两的有命名的域。对于简易的CLENCORE宿主程序,举个例子调整台程序,暗中同意的域名由可实行映象文件的名字组成。其余的域能够在托管代码中应用AppDomain.CreateDomain方法创设,可能在非托管的代码中利用ICO索罗德RuntimeHost接口创造。复杂的宿主程序,例如ASP.NET,对于特定的网址会基于应用程序的数额成立多少个域。

图 2 由CLLX570运行程序创设的域 ↓

必赢网站 1


剖析类型引用

系统域(System Domain)

系统域肩负成立和初步化分享域和暗中认可使用程序域。它将系统库mscorlib.dll载入分享域,何况珍重进程范围之中选拔的包罗也许显式字符串符号。

字符串驻留(string interning)是 .NET Framework
1.第11中学的贰个优化个性,它的拍卖措施显得有个别昏头转向,因为CLRubicon未有给程序集机缘选用此个性。就算如此,由于在全数的施用程序域中对三个一定的符号只保留多个对应的字符串,此性子能够节外省部存储器空间。

系统域还背负产生进程范围的接口ID,并用来创立每个应用程序域的接口虚表映射图(InterfaceVtableMaps)的接口。系统域在进程中保险追踪全体域,并促成加载和卸载应用程序域的功力。


类型

共享域(Shared Domain)

有着不属于别的特定域的代码被加载到系统库SharedDomain.Mscorlib,对于具有应用程序域的客商代码都以不能缺少的。它会被自动加载到分享域中。系统命名空间的为主项目,如Object,
ValueType, Array, Enum, String, and
Delegate等等,在CLRubicon运行程序进度中被事先加载到本域中。顾客代码也得以被加载到这几个域中,方法是在调用CorBindToRuntimeEx时行使由CLENCORE宿主程序钦赐的LoaderOptimization特性。调整台程序也能够加载代码到分享域中,方法是选择System.LoaderOptimizationAttribute天性申明Main方法。分享域还管理二个运用集散地址作为目录的次序集映射图,此映射图作为管理分享程序集信赖关系的查找表,这么些程序集被加载到暗中同意域(DefaultDomain)和任何在托管代码中创立的采纳程序域。非分享的客户代码被加载到私下认可域。


内存分配

默认域(Default Domain)

暗中认可域是采取程序域(AppDomain)的一个实例,平时的应用程序代码在里边运维。就算有个别应用程序须求在运行时创制额外的利用程序域(比如有个别使用插件,plug-in,架构可能扩充注重的周转时期码生成专门的职业的应用程序),大部分的应用程序在运行时期只创制贰个域。全体在此域运维的代码都以在域档期的顺序上有上下文限制。假如三个应用程序有多少个使用程序域,任何的域间访谈会通过.NET
Remoting代理。额外的域内上下文限制新闻方可应用System.ContextBoundObject派生的档期的顺序成立。种种应用程序域有和煦的安全描述符(SecurityDescriptor),安全上下文(SecurityContext)和暗中同意上下文(DefaultContext),还恐怕有温馨的加载器堆(高频堆,低频堆和代理堆),句柄表,接口虚表管理器和程序集缓存。


类型、对象、线程栈、托管堆在运营时的竞相关系

加载器堆(Loader Heaps)

加载器堆的功效是加载不一致的运营时CLMurano部件和优化在域的整整生命期内设有的预制构件。那一个堆的增高基于可预测块,那样能够使碎片最小化。加载器堆不相同于垃圾回收堆(或许对称多管理器上的多个堆),垃圾回收堆保存对象实例,而加载器堆相同的时间保留类型系统。平日访谈的构件如方法表,方法描述,域描述和接口图,分配在屡屡堆上,而少之又少访问的数据结构如EEClass和类加载器及其查找表,分配在低频堆。代理堆保存用于代码访谈安全性(code
access security, CAS)的代理部件,如COM封装调用和平台调用(P/Invoke)。

从高档期的顺序理解域后,大家打算看看它们在一个简短的应用程序的左右文中的物理细节,见
图3。大家在程序运转时停在mc.Method1(),然后使用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();
    }
}

大家的调节台程序,萨姆ple1.exe,被加载到一个名叫”Sample1.exe”的利用程序域。Mscorlib.dll被加载到分享域,可是因为它是骨干系统库,所以也在系统域中列出。每一种域会分配三个往往堆,低频堆和代理堆。系统域和分享域使用同样的类加载器,而暗许应用程序使用自个儿的类加载器。

输出没有体现加载器堆的保存尺寸和已交由尺寸。高频堆的开始化大小是32KB,每趟提交4KB。SOS的输出也不曾显得接口虚表堆(InterfaceVtableMap)。每一种域有二个接口虚表堆(简称为IVMap),由友好的加载器堆在域开头化阶段成立。IVMap保留大小是4KB,开端时提交4KB。大家将会在持续部分研商项目布局时探究IVMap的意思。

图2
展现默许的进程堆,JIT代码堆,GC堆(用于小目的)和大指标堆(用于大小也正是照旧超越8四千字节的目的),它表明了那几个堆和加载器堆的语义差别。即时(just-in-time,
JIT)编译器发生x86指令并且保留到JIT代码堆中。GC堆和大目的堆是用来托管对象实例化的垃圾堆回收堆。

  本文将表达 PE、Windows
加载器、应用程序域、程序集清单、元数据、类型、对象、线程栈、托管堆等,与运作时的相互关系。因此,笔者第一写了多个轻巧德姆o 用于调节和测量试验,其代码如下:

花色原理

品类是.NET编制程序中的基本单元。在C#中,类型能够接纳class,struct和interface关键字打开宣示。大好多体系由技士显式创制,可是,在特意的并行操作(interop)景况和长途对象调用(.NET
Remoting)场馆中,.NET
CL奥德赛会隐式的发出类型,那么些发生的品种包罗COM和运行时可调用封装及传输代理(Runtime
Callable Wrappers and Transparent Proxies)。

我们经过一个满含对象援引的栈开端商讨.NET类型原理(标准地,栈是二个对象实例起初生命期的地点)。
图4中体现的代码包罗二个简约的顺序,它有贰个调节台的入口点,调用了二个静态方法。Method1创办一个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以一定大小(4字节DWO福特ExplorerD)保存在栈中,包罗了在常常GC堆中分配的对象的地方。对于价值观C++,那是目的的指针;在托管世界中,它是目的的援用。不管怎样,它富含了三个对象实例的地点,大家将接纳术语对象实例(ObjectInstance)描述对象援引指向地址地点的数据结构。

图5 SimpleProgram的栈结商谈堆

必赢网站 2

相似GC堆上的smallObj对象实例包括叁个名叫 _largeObj
的字节数组(注意,图中展现的分寸为85016字节,是实际的储备大小)。CL讴歌ZDX对抢先或等于8四千字节的靶子的拍卖和小目的分歧。大目的在大指标堆(LOH)上分红,而小指标在形似GC堆上创造,那样能够优化对象的分配和回收。LOH不会回降,而GC堆在GC回收时开展削减。还会有,LOH只会在一起GC回收时被回收。

smallObj的指标实例包蕴类型句柄(TypeHandle),指向对应档期的顺序的方法表。每一种申明的品类有三个方法表,而平等品种的具有指标实例都对准同贰个方法表。它包括了类别的表征新闻(接口,抽象类,具体类,COM封装和代理),达成的接口数目,用于接口分派的接口图,方法表的槽(slot)数目,指向相应实现的槽表。

措施表指向一个名称为EEClass的主要数据结构。在方式表创建前,CL中华V类加载器从元数据中成立EEClass。
图4中,SmallClass的法门表指向它的EEClass。那几个构造指向它们的模块和次序集。方法表和EEClass经常分配在共享域的加载器堆。加载器堆和动用程序域关联,这里涉及的数据结构一旦被加载到里面,就直到应用程序域卸载时才会熄灭。并且,私下认可的使用程序域不会被卸载,所以那个代码的生存期是结束CL奥迪Q5关闭截至。

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 不是指向指标实例的启幕地点,而是有二个 DWOTiggoD 的偏移量( 4
字节)。此 DWOHighlanderD 称为对象头,保存贰个对准 SyncTableEntry 表的目录(从 1
起初计数的 syncblk
编号。因为经过索引入行三番两次,所以在急需增添表的高低时, CLR能够在内部存款和储蓄器中移动那几个表。 SyncTableEntry 维护二个反向的弱援引,以便 CLTiggo可以追踪 SyncBlock 的全体权。弱援引让 GC
能够在未有别的强引用存在时回收对象。 SyncTableEntry 还保存了一个针对性
SyncBlock
的指针,饱含了很少必要被三个对象的装有实例使用的卓有效率的新闻。那些新闻满含对象锁,哈希编码,任何转变层
(thunking) 数据和平运动用程序域的目录。对于超越44%的目的实例,不会为实在的
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 对象实例布局
必赢网站 3

在以上代码中, smallObj 会采取 0 作为它的初始的 syncblk 编号。 lock
语句使得 CLRubicon 成立贰个 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 地址在内部存款和储蓄器窗口展示对象实例。前 4 个字节是 syncblk
编号。因为大家尚无用别的共同代码应用此实例(也未有访问它的哈希编码),
syncblk 编号为 0 。保存在栈变量的指标实例,指向初步地点的 4
个字节的偏移处。字节变量 b1,b2,b3 和 b4 被三个接三个的排列在一同。七个short 类型变量 s1 和 s2 也被排列在一起。字符串变量 str 是贰个 4 字节的
OBJECTREF ,指向 GC
堆中分红的骨子里的字符串实例。字符串是一个极其的品类,因为兼具满含一样文字标志的字符串,会在程序集加载到进程时指向四个大局字符串表的同等实例。那个历程称为字符串驻留(
string interning ),设计指标是优化内部存储器的运用。大家事先曾经提过,在 NET
Framework 1.1 中,程序集不可能采取是或不是选拔那几个进度,即使未来版本的 CL本田UR-V大概会提供这么的力量。

图 8 Debugger Memory Window for Object Instance
必赢网站 4

就此默许景况下,成员变量在源代码中的词典顺序没有在内部存储器中保持。在竞相操作的场所下,词典顺序必须被保存到内部存储器中,那时能够利用
StructLayoutAttribute 天性,它有一个 LayoutKind 的枚举类型作为参数。
LayoutKind.Sequential 可感到被封送( marshaled
)数据保持词典顺序,就算在 .NET Framework 1.第11中学,它从不影响托管的布局(可是 .NET Framework 2.0
可能会如此做)。在竞相操作的景况下,倘令你实在必要万分的填充字节和显示的调节域的次第,
LayoutKind.Explicit 能够和域等级次序的 FieldOffset 特性一齐行使。

看完底层的内部存款和储蓄器内容后,大家采用 SOS 看看对象实例。一个可行的下令是
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 的实例只含有贰个DWOPRADOD 的靶子引用。 SimpleClass 的实例变量只占用 28 字节,别的 8
个字节满含项目句柄( 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)

倘使你从指标图的全局大小( 72 字节)减去 SimpleClass 的尺寸( 36
字节),就能够获得 str 的深浅,即 36 字节。让我们输出 str
实例来申明这些结果:

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

倘诺您将字符串实例的尺寸(36字节)加上SimpleClass实例的深浅(36字节),就足以博得ObjSize命令报告的总大小72字节。

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

针对方法表的等级次序句柄在syncblk编号后分配。在目的实例成立前,CLOdyssey查看加载类型,若无找到,则张开加载,得到方法表地址,创制对象实例,然后把项目句柄值追加到对象实例中。JIT编写翻译器发生的代码在张开药格局分派时行使项目句柄来定位方法表。CLR在要求史能够经过艺术表反向访问加载类型时利用项目句柄。

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更加多信息,参照他事他说加以考察这里

一 加载.NET 程序集

方法表

各种类和实例在加载到利用程序域时,会在内部存款和储蓄器中经过艺术表来表示。那是在对象的首先个实例创立前的类加载活动的结果。对象实例表示的是景况,而艺术表表示了表现。通过EEClass,方法表把对象实例绑定到被语言编写翻译器发生的照耀到内部存款和储蓄器的元数据结构(metadata
structures)。方法表满含的音讯和外挂的音讯方可通过System.Type访谈。指向方法表的指针在托管代码中得以经过Type.RuntimeTypeHandle属性获得。对象实例包括的品类句柄指向方法表最早地方的偏移处,偏移量暗中认可情状下是12字节,满含了GC音信。大家不筹划在此间对其进展座谈。

图 9
显示了点子表的优良布局。大家会注脚项目句柄的有个别第一的域,不过对于截然的列表,请参见此图。让我们从基实例大小(Base
Instance Size)起先,因为它直接涉及到运转时的内部存款和储蓄器状态。

图 9 方法表布局

必赢网站 5

  在Windows上运转的次序能够经过各类不相同的办法开展运营。Windows
担负管理全数的相关专门的工作,包蕴安装进度地址空间、加载可执行程序,以及提示管理器开首推行等。当计算机先河实践顺序指令时,它将一贯实行下去,直到进度退出。

基实例大小

基实例大小是由类加载器总结的对象的高低,基于代码中评释的域。以前早就商讨过,当前GC的兑现内需八个最少12字节的对象实例。就算一个类没有概念任何实例域,它起码含有额外的4个字节。此外的8个字节被对象头(可能包蕴syncblk编号)和体系句柄占用。再说一遍,对象的深浅会面对StructLayoutAttribute的震慑。

看看图3中显得的MyClass(有三个接口)的艺术表的内部存款和储蓄器快速照相(Visual
Studio .NET
贰零零贰内存窗口),将它和SOS的输出进行比较。在图9中,对象大小位于4字节的偏移处,值为12(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

  以后扩展我们对 PE 文件的认知,PE
格式是 Windows
可实践程序的文件格式,可实行程序包涵:*.exe、*.dll、*.obj、*.sys
等。为了扶助.NET,在 PE
文件格式中扩展了对前后相继集的支撑,PE文件格式如下:

方法槽表(Method Slot Table)

在艺术表中蕴含了二个槽表,指向种种艺术的陈述(MethodDesc),提供了类其他行为本领。方法槽表是基于方法完结的线性链表,依照如下顺序排列:承接的虚方法,引进的虚方法,实例方法,静态方法。

类加载器在脚下类,父类和接口的元数据中遍历,然后创造方法表。在排列进程中,它替换全部的被遮住的虚方法和被隐形的父类方法,创立新的槽,在供给时复制槽。槽复制是不可缺少的,它能够让各类接口有谐和的小小的vtable。不过被复制的槽指向平等的大要完结。MyClass富含接口方法,一个类构造函数(.cctor)和对象构造函数(.ctor)。对象构造函数由C#编写翻译器为全部未有显式定义构造函数的对象自动生成。因为大家定义并开始化了二个静态变量,编写翻译器会变卦四个类构造函数。图10呈现了MyClass的艺术表的布局。布局展现了10个法子,因为Method2槽为接口IVMap实行了复制,下边我们博览会开座谈。图11显示了MyClass的点子表的SOS的输出。

图10 MyClass MethodTable Layout
必赢网站 6

图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()

任何类型的起来4个点子总是ToString, Equals, GetHashCode, and
Finalize。那些是从System.Object承接的虚方法。Method2槽被举行了复制,可是都针对同样的法子描述。代码展现定义的.cctor和.ctor会分别和静态方法和实例方法分在一组。

必赢网站 7

办法描述(MethodDesc)

方式描述(MethodDesc)是CL昂科威知道的情势实现的叁个包装。有三种档期的顺序的方法描述,除了用于托管达成,分别用于区别的并行操作完毕的调用。在本文中,大家只考察图3代码中的托管方法描述。方法描述在类加载进度中产生,伊始化为指向IL。每一种方法描述包括八个预编写翻译代理(PreJitStub),肩负触发JIT编写翻译。图12呈现了七个金榜题名的布局,方法表的槽实际上指向代理,并非实在的艺术描述数据结构。对于实际的不二等秘书技描述,那是-5字节的舞狮,是各种方法的8个叠合字节的一片段。那5个字节包罗了调用预编写翻译代理程序的一声令下。5字节的撼动能够从SOS的DumpMT输出从察看,因为方法描述总是方法槽表指向的岗位前面包车型地铁5个字节。在首先次调用时,会调用JIT编写翻译程序。在编写翻译达成后,满含调用指令的5个字节会被跳转到JIT编写翻译后的x86代码的任务跳转指令覆盖。

图 12艺术描述

必赢网站 8

图12的主意表槽指向的代码实行反汇编,显示了对预编写翻译代理的调用。以下是在
Method2 被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

在此地方,独有开始5个字节是代码,剩余字节满含了Method2的方法描述的多少。“!u”命令不通晓这或多或少,所以生成的是乱套的代码,你能够忽略5个字节后的具有东西。

CodeOrIL在JIT编写翻译前带有IL中方法落成的相对虚地址(Relative Virtual
Address
,路虎极光VA)。此域用作标记,表示是不是IL。在按须求编写翻译后,CLQashqai使用编写翻译后的代码地址更新此域。让大家从列出的函数中精选二个,然后用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完毕。让我们看方法表别的一个复杂的地点:接口实现。它包裹了布局进度具有的复杂,让托管意况感到那点看起来大致。然后,大家将表达接口怎么着开展布局和基于接口的情势分派的正合分寸职业方法。

  为了帮助PE映像的实行,在PE的头满含了二个域可以称作AddressOfEntryPoint。那几个域表示 PE
文件的入口点(EntryPoint)的职责。在.NET程序聚集,那几个值指向.text
段中的一小段存根(stub)代码(“JMP _CorExeMain”)。当.NET
编写翻译器生成程序集时,它会在 PE
文件中加进叁个数码目录项。具体来讲,那个数目目录项的目录为
15,个中满含了 CL路虎极光 头的岗位和尺寸。然后,依照那些职责在 PE
文件中找到位于.text 段中的 CLPAJERO 头。在 CLHighlander 头中富含了一个结构
IMAGE_COR20_HEADE卡宴。在那几个布局中包蕴了多数音信,比方托管代码应用程序入口点,目标CL汉兰达的主版本号和从版本号,以及程序集的强名称签字等。依据那一个布局中满含的消息,Windows
能够知道要加载哪个版本的 CLR 以及有关程序集我的一对音信。在.text
段中还满含了前后相继集的元数据表,IL以及非托管运营存根码。非托管运维存根码富含了由
Windows 加载器施行以运维 PE 文件施行的代码。

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

在点子表的第12字节偏移处是三个首要的指针,接口虚表(IVMap)。如图9所示,接口虚表指向三个施用程序域档期的顺序的映射表,该表以进度等级次序的接口ID作为目录。接口ID在接口类型第叁遍加载时创制。各样接口的贯彻都在接口虚表中有三个笔录。如若MyInterface1被三个类完毕,在接口虚表表中就有八个记录。该记录会反向指向MyClass方法表内含的子表的开端位置,如图9所示。那是接口方法分派发生时选取的援用。接口虚表是凭借方法表内含的接口图新闻创造,接口图在章程表布局进度中基于类的元数据创制。一旦类型加载成功,唯有接口虚表用于方法分派。

第28字节地方的接口图会指向内含在艺术表中的接口音讯记录。在这种场所下,对MyClass达成的多个接口中的每多少个皆有两条记下。第一条接口新闻记录的发端4个字节指向MyInterface1的品类句柄(见图9图10)。接着的WOLX570D(2字节)被叁个表明占用(0象征从父类派生,1象征由方今类达成)。在申明后的WOLacrosseD是四个始发槽(Start
Slot),被类加载器用来布局接口落成的子表。对于MyInterface2,开头槽的值为4(从0发轫编号),所以槽5和6指向完成;对于MyInterface2,最初槽的值为6,所以槽7和8指向实现。类加载器会在急需时复制槽来爆发这么的效果:种种接口有友好的贯彻,然则物理映射到均等的措施描述。在MyClass中,MyInterface1.Method2和MyInterface2.Method2会指向一样的实现。

依据接口的秘技分派通过接口虚表进行,而一直的议程分派通过保留在每家每户槽的措施描述地址进行。如在此之前聊到,.NET框架使用fastcall的调用约定,最先2个参数在大概的时候日常经过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编写翻译器把艺术描述的地方直接写到代码中。基于接口的分摊通过接口虚表产生,和平昔分派相比需求一些外加的通令。八个下令用来赢得接口虚表的地方,另八个获得方式槽表中的接口完毕的发端槽。何况,把多个对象实例转变为接口只需求拷贝this指针到目的的变量。在图2中,语句“mi1=mc”使用贰个下令把mc的指标援用拷贝到mi1。

  当 Windows 加载八个.NET
程序集时,mscoree.dll
的_CorExeMain(或者是_CorDllMain,决议于加载的是可执行文件照旧库)
函数被第一个调用,以运维 CL昂Cora。 mscoree.dll 在运营 CLOdyssey时将举行一多级操作:

虚分派(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号槽发生,能够在调节和测量检验器的内部存款和储蓄器窗口(如图10所示)和DumpMT的输出见到那点。

  (1) 通过查看 PE
文件中的元数据(具体来说是 CLLX570 头中的 MajorRuntimeVersion 和
MinorRuntimeVersion)找寻.NET 程序集是基于哪个版本的 CLLacrosse 创设的。

静态变量(Static Variables)

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

  (2) 寻觅 OS 中准确版本 CL宝马X3的路子。

EEClass

EEClass在格局表创造前开首生活,它和艺术表组成起来,是项目评释的CL本田CR-V版本。实际上,EEClass和情势表逻辑上是三个数据结构(它们一同表示一个品类),只可是因为运用频度的不等而被分手。通常使用的域放在方法表,而不平日应用的域在EEClass中。那样,须要被JIT编写翻译函数使用的音讯(如名字,域和偏移)在EEClass中,但是运转时索要的音信(如虚表槽和GC消息)在艺术表中。

对每二个门类会加载一个EEClass到使用程序域中,满含接口,类,抽象类,数组和组织。各种EEClass是贰个被推行引擎跟踪的树的节点。CL奥迪Q5使用那个互联网在EEClass结构中浏览,其指标包蕴类加载,方法表布局,类型验证和类型转变。EEClass的子-父关系基于承继等级次序创设,而父-子关系基于接口档案的次序和类加载顺序的结缘。在施行托管代码的经过中,新的EEClass节点被加入,节点的涉及被补充,新的关联被确立。在互联网中,相邻的EEClass还大概有三个水平的关系。EEClass有多个域用于管理被加载类型的节点关系:父类(Parent
Class),相邻链(sibling chain)和子链(children
chain)。关于图4中的MyClass上下文中的EEClass的语义,请参谋图13

图13只彰显了和这些商量有关的一些域。因为我们忽略了布局中的一些域,大家向来不在图中正好显示偏移。EEClass有多少个直接的对于艺术表的援用。EEClass也针对在暗中同意使用程序域的每每堆分配的办法描述块。在章程表成立时,对进程堆上分配的域描述列表的二个援用提供了域的布局消息。EEClass在动用程序域的低频堆分配,那样操作系统可以更加好的进行内部存款和储蓄器分页管理,因而降低了职业集。

图13 EEClass 布局

必赢网站 9

图13中的别的域在MyClass(图3)的上下文的意义无庸赘述。大家以往探望使用SOS输出的EEClass的真的的情理内存。在mc.Method1代码行设置断点后,运转图3的程序。首先利用命令Name2EE得到MyClass的EEClass的地点。

!Name2EE C:WorkingtestClrInternalsSample1.exe MyClass

MethodTable: 009552a0
EEClass: 02ca3508
Name: MyClass

Name2EE的首先个参数时模块名,能够从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有8个虚表槽(能够被虚分派的办法)。即使Method1和Method2不是虚方法,它们能够在通过接口进行分摊时被以为是虚函数并出席到列表中。把.cctor和.ctor加入到列表中,你会获得总共10个主意。最后列出的是类的多个静态域。MyClass未有实例域。另外域无庸赘述。

  (3) 加载并开头化 CLENVISION。

结论

大家关于CLKoleos一些最关键的内在的琢磨旅程终于终止了。显著,还可能有繁多标题亟需涉及,何况须求在更加深的档案的次序上议论,不过大家期望那足以帮助你看来事物如何做事。这里提供的居多的新闻恐怕会在.NET框架和CLWrangler的新生版本中更改,但是固然本文提到的CL瑞鹰数据结构可能变动,概念应该保险不改变。

  在 CLEnclave 被开端化之后,在 PE 文件的 CL汉兰达头中就可以找到程序集的入口点(Main())。然后,JIT
伊始编写翻译并实施入口点。

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

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

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

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

  (4) 将实行调整转移到
mscoree.dll 中的函数 _CorExeMain 中,这一个函数将运营 CLOdyssey并把进行调控转移到程序集的入口点。

   注意,在 Windows XP
及随后版本中,对加载器进行了优化,使其能够分辨出贰个 PE 文件,是不是是.NET
程序集。那样,在加载叁个.NET 程序集时,就不再要求经过存根函数调用
mscoree.dll的导入函数了,而是成为自动加载 CLEscort。

二 应用程序域

  Windows 使用进度来隔绝应用程序,.NET
在此基础上特别引人了另一种逻辑隔开分离层,即利用程序域。构造和管理进程的支出是充裕高的,应用程序域非常大地裁减在开创与销毁隔开分离层时所需的开辟。

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

必赢网站 10

  在其余运维了 CLSportage 的 Windows
过程中都会定义二个或多少个使用程序域,在那么些域中带有了可实施代码、数据、元数据结构以及财富等。除了进度自个儿的护卫体制外,应用程序域还进一步引人了以下尊敬体制:

  • 二个用到程序域中的错误代码不会影响到同二个进程中另一个使用程序域中运作的代码。
  • 三个接纳程序域中的代码不可能向来访谈另二个施用程序域中的能源。
  • 各类应用程序域中都能够安顿与代码特定的音信,如安全设置。

  对于未有显式创造应用程序域的应用程序来讲,CL奥迪Q3会创制七个应用程序域:系统选用程序域、分享利用程序域、暗中同意使用程序域。

(一) 系统利用程序域

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

  • 创办另外七个利用程序域(分享应用程序域、暗许使用程序域)。

  • mscoree.dll加载到分享利用程序域中。
  • 笔录进度中全体别的的行使程序域,包含提供加载、卸载应用程序域等职能。
  • 笔录字符串池中的字符串常量,由此同意放肆字符串在各种进程中都留存三个别本。
  • 起首化特定项指标十三分。

(二) 分享应用程序域

  在分享利用程序域中隐含的是与使用程序域非亲非故的代码。mscoree.dll
将被加载到那几个利用程序域中,别的还包涵在 System
命名空间中的一些主干类型(eg.String、Array等)。在超越55%状态下,非顾客代码将被加载到分享应用程序域中。启用了
CLKuga 的利用程序域能够由此加载器的优化属性来注入顾客代码。

(三) 默许应用程序域

  平常,.NET
程序在暗中同意使用程序域中运行。位于私下认可使用程序域中的全体代码都唯有在那个域中才是卓有成效的。由于采取程序域达成了一种逻辑而且可信赖的分界,因而任何超出应用程序域的拜谒操作都必得通过.NET
远程对象来扩充。

  下图体现了本文起初创建的 德姆o
的行使程序域消息:

必赢网站 11

三 分析类型引用

  运转应用程序时,CLPRADO会加载并初阶化它。然后 CLENCORE 读取程序集的 CLEscort头,查找标志了应用程序入口的措施(Main())的 MethodDefToken。然后,CL摩尔根Plus 4会搜索 MethodDef 元数据表,找到该措施的 IL 代码在文书中的偏移量,把这个IL 代码 JIT
编写翻译为地点代码。编译时会对代码实行验证以担保项目安全性。最后,将实行本地代码。在
JIT 编写翻译时,CL奇骏会检核查项目和分子的兼具引用,并加载定义了它们的程序集(若无加载),CLEnclave必需牢固并加载程序集。剖析一个引用的花色时,CL传祺可能在以下三个地方找到类型:

  • 同三个文书 
  • 不一致文件,同样程序集
  • 差异文件,分裂程序集

  分析多个类别引用时要是发送任何不当,如找不到文件、文件不能够加载、哈希值不相同盟等,就能够抛出极其。下图演示了体系绑定的进度:

必赢网站 12

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

  对于 CL翼虎来讲,全体程序集都以基于名称、版本、语言文化、公钥来标志的。不过,GAC
依据名称、版本、语言文化、公钥和 CPU 架构来标志程序集。在 GAC
中寻找程序集时,CL奥迪Q5剖断应用程当前在什么项目标经过中运转(三十三个人、六贰十一人)。然后,CL凯雷德首先寻觅程序集的这种 CPU 架构专项使用版本,若无找到,就索求不区分 CPU
的本子。

四 类型

  类型是.NET
程序中的基本编制程序单元。在.NET
应用程序中,要么使用自定义的品种,要么接纳现存的花色。类型分为两类:值类型和援用类型。值类型是指保存在线程栈上的体系,包含:枚举、结构以及简单类型(如
int、bool、char等)。平常,值类型是一些据为己有内部存款和储蓄器空间异常的小的品类。另一类别型叫做援用类型,它是在堆上分配的,并由垃圾回收器(GC)担当管理。在援引类型中也足以蕴涵值类型,在这种气象下,值类型将长期以来位于堆上而且由垃圾搜聚器来管理。

  托管堆上对象的构造如下:

必赢网站 13

  在托管堆上的各类对象实例中都包罗了以下消息:

  • 同步块(sync
    block):同步块可以是贰个位掩码,也能够是由 CL库罗德维持的联合块表中的索引,个中带有了关于指标自己的支援消息。
  • 类型句柄(type handle):类型句柄是
    CLENCORE类型系统的根底单元,能够用来对托管堆上的项目举办总体描述。
  • 对象实例:在同步块索引和类型句柄之后随就是实在的对象数据。

  下图显示了 Demo 的 Circle
对象的内容:

必赢网站 14

(一) 同步块表

   在托管堆上每一个对象的前头都有三个一块块索引,它指向
CLGL450中个人堆上的同台块表。在一道块表中包含的是指向各类同步块的指针,在一同块中包括了多数新闻,如指标的锁、互用性数据、应用程序域索引、对象的散列码(hash
code)等。当然,在对象中也只怕不分包别的共同块数据,此时的一路块索引值为0。须要注意的是,在联合签字块中并不一定只含有简单的目录,也得以分包对象的其他帮扶音信。

  (在行使索引时要小心,CLPAJERO能够私行移动/增加同步块表,同不时候却不自然对持有富含一块块的目的头进行调度。)

(二) 类型句柄

  引用类型的富有实例都被放在托管堆上,那个堆是由
GC
来决定。在全部的实例中都蕴含了三个门类句柄。轻便地说,类型句柄指向的是某些项指标方法表。在艺术表中包罗了各个元数据,它们完整地汇报了那个项目。下图表达了办法表的完好内部存款和储蓄器布局:

必赢网站 15

  类型句柄是 CL奥德赛类型系统中的粘合剂,它把目的实例及其全数的相关项目数据涉嫌起来。对象实例的门类句柄存储在托管堆上,它是二个指南针,指向类型的方法表。在艺术表中含有了关于目的类型的大方新闻,满含针对任何主要CL帕杰罗 数据结构(如
EEClass)的指针。在类型句柄指向的首先类数据中隐含了有关项目小编的一对新闻(如标志、大小、方法数量、父方法表等)。下三个要专一的域是二个指针,指向一个EEClass。方法表的下一部分也是三个指南针,指向与品类相关的模块消息。在剩下的域中蕴涵了项目标虚方法表。需求静心的是,在艺术表中的片段艺术指针恐怕会针对非托管代码。出现这种场馆包车型客车原因是,一些情势只怕还尚无被
JIT 编译器编译。事实上,运行编写翻译进程的 JIT
存根代码是一段非托管代码,当方法未有被 JIT
编写翻译器编写翻译时,它会指向这段非托管代码,在编写翻译之后会把实行调控权转移到新编写翻译生成的代码。

(三) 方法描述符

  在艺术表中包蕴了虚方法表,里面富含了一些针对隐蔽在项目方法背后的代码的指针。虚方法表中含有了指向代码的指针,那些措施本人能够自动描述,那都归功于方法描述符。在艺术描述符中包罗了有关艺术的详细消息,如方法的文书表示、它所在的模块、标志以及贯彻方式的代码地址。

  下图体现了 德姆o 的 Circle
对象的方法表及办法描述符:

必赢网站 16

  查看 GetCircumference 方法的
IL:

必赢网站 17

  进一步赢得情势的新闻:

必赢网站 18

(四) 模块

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

必赢网站 19

(五) 元数据符号

  CLMurano的元数据以表格的花样积攒在运作时引擎中,元数据符号是贰个4字节的值,其布局如下:

必赢网站 20

  查看 Circle
的办法表能够观望元数据符号:

必赢网站 21

  值为 0两千004
的元数据符号能够分解为:指向类型定义表中的第2个目录。

(六)EEClass

  EEClass
数据结构能够用作是措施表的三个逻辑等价物,由此它能够视作贯彻 CL福特Explorer类型系统自描述性的一种体制。本质上,EEClass
和情势表是三种天差地远的组织,但从逻辑来看,它们都意味着一致的概念。之所以分成那二种数据结构,是因为
CL讴歌RDX使用类型域的高频程度分歧。频仍利用的域被保存到点子表中,而不频仍使用的域被保存到
EEClass 中。EEClass 的大概结构如下:

必赢网站 22

  C# 中的等级次序结构在 EEClass
中同样适用。当 CL昂Cora 加载类型时,会创立三个品类的 EEClass
节点等级次序结构,当中包蕴了指向父节点和兄弟节点的指针,那样就可以遍历整个档期的顺序结构。EEClass
中的方法描述块域,包括了八个指南针,指向类型中的第一组方法描述符,那样就会遍历大肆类型中的方法描述符。在每组方法描述符中又含有指向链表中下一组方法描述符的指针。

  查看 Circle 的 EEClass:

必赢网站 23

五 内部存款和储蓄器分配

  CL陆风X8管理的内部存款和储蓄器重要分为3部分,如下:

  • 线程栈
    用于分配值类型实例。线程栈首要由操作系统管理,而不受垃圾采摘器的决定,当班值日类型实例所在措施停止时,其积攒单位机关释放。栈的实践效用高,但存款和储蓄体量有限。
  • 微型对象堆(SOH) 用于分配小指标实例。纵然援引类型对象的实例大小小于8伍仟字节,实例将被分配在SOH堆上,当有内部存款和储蓄器分配依然回收时,垃圾收集器大概会对SOH堆进行削减。
  • 大型对象堆(LOH) 用于分配大目的实例。假若援用类型对象的实例大小相当的大于8陆仟字节时,该实例将被分配到LOH堆上,不一致于SOH堆,垃圾搜聚器不会对LOH堆举行削减。


类型、对象、线程栈、托管堆在运营时的并行沟通

  运转 Demo
时,会运行贰个进程,因为程序本人是单线程的具备独有八个线程。一个线程被创造时会分配到
1MB
大小的栈。那一个栈的半空中用于向方法传递实参,并用以方法内部定义的一些变量。

  以往,Windows 进度早就起步,CLTucson已经加载到当中,托管堆已伊始化,何况已开立一个线程(连同它的 1MB
栈空间)。未来一度进来 Main() 方法,霎时将在施行 Main
中的语句,所以栈和堆的事态如下图所示(为了简化暗暗表示图,作者只画出了自定义的花色):

必赢网站 24

  当 JIT 编写翻译器将 Main() 方法的 IL
代码调换开支地 CPU 指令时,会小心到其里面援引的全部项目。这一年,CL帕杰罗要力德阳义了那一个类别的具备程序集都已经加载。然后利用程序集的元数据,CLLacrosse提取与那个品种有关的新闻,并创办一些数据结构来表示项目笔者。在线程试行业地代码前,会成立所需的有所目的。下图展现了在
Main 被调用时,创设项目对象后的场馆:

必赢网站 25

  当 CLLX570鲜明方法须要的有着类型对象都已经开立,并且 Main
的代码已经编写翻译之后,就同意线程开端奉行编写翻译好的地头代码。首西子行的是
“Circle circle = new Circle(4.0);”,那会成立多少个 Circle
类型的一部分变量,并为其赋值。当调用构造函数时,会在托管堆中开创 Circle
的实例。任几时候在堆上新建叁个目的 CLMurano都会活动初阶化内部类型对象指针成员,将它引用与指标对应的档期的顺序对象。别的,CL奔驰M级会先开始化同步块索引,将对象的具备实例字段设置为 null 或
0,再调用类型的构造器。new 操作符会再次来到 Circle
对象的内部存款和储蓄器地址,该地点将保留在局地变量 circle
中(在线程栈上)。此时的状态如下图:

必赢网站 26

  接着实施“Console.WriteLine(circle.ToString());”。ToString()
方法是二个虚方法,在调用虚方法时,JIT
编写翻译器要在章程中生成一些相当的代码,方法每趟调用时都会施行那么些代码。那个代码首先检查发出调用的变量,然后跟随地址来到产生调用的靶子。在本例中,变量
circle 援用的是 Circle
类型的三个对象。然后,代码检核对象内部的“类型句柄”成员,那个成员指向对象的实际上类型。然后,代码在项目对象的方法表中寻找援用了被调用方法的记录项,对艺术开展
JIT 编写翻译(借使急需),再调用 JIT 编写翻译过的代码。就本例来讲,调用的是
Circle 类型的 ToString 完结。(在调用非虚方法时,JIT
编写翻译器会找到调用对象的项目对应的项目对象。借使该类型未有概念非常情势,JIT
编写翻译器就能记忆类档案的次序结构,一直回溯到
Object,并在沿途的各个门类中查找该办法。)

  WriteLine(string)
是静态方法。调用贰个静态方法时,CL智跑会定位与静态方法的连串对应的品类对象。然后,JIT
编写翻译器在品种对象的不二秘诀表中搜索与被调用的格局对应的记录项,对艺术开展 JIT
编写翻译(如若要求),再调用 JIT
编写翻译的代码。综上所述,“Console.WriteLine(circle.ToString());”的操作结果如下图所示:

必赢网站 27

  最终,试行“Console.ReadKey();”,与WriteLine(string)
类似,这里就不再赘言。大家能够看出,Circle
类型对象也包括“类型句柄”成员。那是因为项目对象本质上也是指标。CL索罗德创立项目对象时,必须伊始化这个分子。CL传祺 起头在一个进度中运转时,会及时为 mscorlib.dll 中定义的 System.Type
类型创建三个非凡的项目对象。Circle
类型对象是该品种的实例。因而,在初步化时,Circle
类型对象的品种句柄会开首化为对 System.Type
类型对象的援用。如下图所示:

必赢网站 28

  System.Type
类型对象自己也是一个指标,内部的项目句柄指向它自个儿。System.Object 的
GetType 方法再次来到的是积累在钦点对象的花色句柄(是二个指南针)。

相关文章