Object内存核心结构及实现完全剖析(MethodTable、EEClass与MethodDescChunk)

无疑,一个ObjectCLR中的逻辑结构是相当复杂的。
前段时间,写了一篇CLR探索系列:System.Object内存布局模型及实现研究,侧重从System.Object这个基本类的基本内存布局,实现和结构来研究了下。这是远远不够的。今天就从如何存储一个Object中的FieldMethod等信息,这些信息的逻辑组织方式和存储的逻辑结构。

废话不多说,看看就知道了:

 

首先,给一个图:


      
  
    这个图,显示了一个
ObjectMethodTableEEClassMethodDescChunk之间的大致关系。至于一个整体的Object的实现的逻辑结构模型图,结构比较复杂,不好画,google了半天也没找到个现成的,就用这个凑合了。

通常,在内存中,对一个instanceref的这个地址,其实是指向这个instanceMethodTable的。这个内容,我在“System.Object这个基本类的基本内存布局,实现和结构”这篇文章里面也有说,具体是如何实现的可以参照那篇文章。

老规矩,截取点MethdTable里面重要的属性,方法和结构体,总共一个MethodTable的实现,就大概2500多行,(太长了?这仅仅是定义部分),只能截取表示如下了:

 

class MethodTable

{   

    // Low WORD is component size for array and string types, zero otherwise

    DWORD           m_wFlags;

 

    // Base size of instance of this class when allocated on the heap

    DWORD           m_BaseSize;

    //Nmuber of Method
   
WORD            m_wNumMethods; 
   
//Number of Vitural Methods

    WORD            m_wNumVirtuals;
   
WORD            m_wNumInterfaces;

       
   
// LoaderModule. It is equal to the ZapModule in ngened images

    PTR_Module      m_pLoaderModule;   

    PTR_MethodTable m_pParentMethodTable;

 

    // This is the way to create a new method table. Don't try calling new directly.

    // Mehtod tables are also created in array.cpp where an EEClass

    // and MethodTable are created in one fell swoop.  I see no rationale basis for

    // such an approach.

    // Even worse almost exactly the same stuff is duplicated in DynamicMethod.cpp.

    // Worse still almost exactly the same stuff is duplicated in generics.cpp.

    //MethodTableNew函数,MethodTalbe不能直接New一个

    static MethodTable * AllocateNewMT(EEClass *pClass,

                                       DWORD dwVtableSlots,

                                       DWORD dwGCSize,

                                       DWORD dwNumInterfaces,

                                       DWORD numGenericArgs,

                                       DWORD dwNumDicts,

                                       DWORD dwNumTypeSlots,

                                       ClassLoader *pClassLoader,

                                       BaseDomain *pDomain,

                                       BOOL isIFace,

                                       BOOL fHasGenericsStaticsInfo,

                                       BOOL fNeedsRemotableMethodInfo,

                                       BOOL fNeedsRemotingVtsInfo,

                                       BOOL fHasThreadOrContextStatics

                                       , AllocMemTracker *pamTracker

        );

 

    // Return total vtable slots : virtual, static, and instance method slots.

    unsigned GetNumMethods()

    {

        LEAF_CONTRACT;

        return m_wNumMethods;

    }

 

    void SetNumMethods(WORD wNumSlots)

    {

        LEAF_CONTRACT;

        m_wNumMethods = wNumSlots;
   
}


       //
使用专门的数据结构来实现对Table中的可写部分的读写。而这些可写的部分,是定义在另外的一个专门的枚举类型的变量中。

PTR_MethodTableWriteableData m_pWriteableData;

//MethodTable类中最重要的一个定义,包涵了对EEClass的引用。

PTR_EEClass     m_pEEClass;

      

       //在寻找所谓的VTable定义的时候,着实费了一番功夫,最后得到一个结论:在sscli2.0中,去掉了m_Vtable[1]的显示定义。更多的是把Vtable当作一个Thunking Layer来在需要的时候操作。

//为此,MethodTalbe的定义中,还专门定义了一个structInteropMethodTableSlotData)来实现模拟就得Vtable结构对COM接口的访问。在需要使用到类似Vtable的时候直接计算某个SlotOffset,并且使用专门的Accessor来进行访问。截取两个方法来说明这点:


   
inline PTR_SLOT GetVtable()

    {

        LEAF_CONTRACT;

        return PTR_SLOT((PTR_HOST_TO_TADDR(this) + TADDR(GetVtableOffset())));

    }

 

    static inline DWORD GetVtableOffset()

    {

        LEAF_CONTRACT;

        return (sizeof(MethodTable));

    }

}


      
从下图中也可以很明显的看出MethodTable的结构以及其部分字段的表示的含义:


 

       最后给出一个MethodTable中重要的数据结构:


struct ThreadAndContextStaticsBucket

{

    // Offset which points to the TLS storage. Allocated lazily - -1 means no offset allocated yet.

    DWORD m_dwThreadStaticsOffset;

    // Offset which points to the CLS storage. Allocated lazily - -1 means no offset allocated yet.

    DWORD m_dwContextStaticsOffset;

    // Size of TLS fields

    WORD m_wThreadStaticsSize;

    // Size of CLS fields

    WORD m_wContextStaticsSize;

};

typedef DPTR(ThreadAndContextStaticsBucket) PTR_ThreadAndContextStaticsBucket;

 

       在上面的部分属性中,可以看到,一个Object的信息,并不是所有的都保存在MethodTalbe中的。这样分层设计的目的,还是一个字:效率。把一个Object使用的最频繁的部分,保持在MethodTable中,而使用的稍微不频繁的部分,保持到EEClass中,这部分信息,从名字上面就可以看出,主要是包涵了CLR执行引擎所需要的对一个Object的控制信息。也就是如下图所示,各个不同的部分所包涵的内容应用的重点和频率都不同:

      


       接着,给出EEClass的一些重要的数据结构:


Class EEClass

{

       //得到这个Object运行的一些基本环境。

       BaseDomain * GetDomain();

    Assembly * GetAssembly();

    Module* GetLoaderModule();

    Module* GetZapModule();

 

       //point out the Module which contains this Object and its EEClass

    PTR_Module m_pModule;

    mdTypeDef m_cl;

       //important property!point to the MethodTable of this Object.

    PTR_MethodTable m_pMethodTable;

 

    // NOTE: Place items that are WORD sized or smaller together,

       //otherwise padding will be used implicitly by the C++ compiler

    WORD m_wCCtorSlot;

    WORD m_wDefaultCtorSlot;

 

    BYTE m_NormType;

       //number of instance Fields

    WORD m_wNumInstanceFields;

       //number of static fields

    WORD m_wNumStaticFields;

    WORD m_wNumHandleStatics;

    WORD m_wNumBoxedStatics;

       //implies the number of Object ref in a instance.

       //GCDesc结构,也就是一个Object实现的时候的头部,Object Header可能会引用到,同时JIT也有可能引用到这个属性。

    WORD m_wNumGCPointerSeries;

 

    DWORD m_cbModuleDynamicID;

    DWORD m_cbNonGCStaticFieldBytes;

    DWORD m_dwNumInstanceFieldBytes;

 

       //这个属性表面了对于一个Object里面的Fields,是在EEClass里面把每个Field对应的FieldDesc类连接在了一起。

FieldDesc *m_pFieldDescList;

 

    DWORD m_dwAttrClass;

    volatile DWORD m_VMFlags;

    SecurityProperties m_SecProps;

       

       //这里定义了MethodDescChunk,从最上面的图可以看到,从一个EEClass中是连接到MethodDescThunk链表上面的。比较重要的一个结构。

    PTR_MethodDescChunk m_pChunks;

}


      
再回头看一下,MethodTablem_VtableVtable in MethodTable),MethodDescChunks这个Chain是如何连接起来的就十分清楚了:

 

       最后,看看MethodDescChunk这个描述每一个Method的大块头的结构如何,这里,就不在分析一个MethodDescChunk里面究竟是如何实现的了:

      

MethodDescChunk的头部是上面的蓝色的部分。Prestub是中间的斜线的部分,而对每个方法的描述的部分,是上图的白色的区域,可以看到,包涵了这个方法的IL代码,SlotNumber,和一些标识位。每一行,是一个DWORD类型。

注意:m_op中,包涵了对这个方法的调用,是call JIT来进行实时编译,还是jump到一个已经编译好了的地方,m_op就是标识这两种不同的操作类型。而m_target里面,则是和m_op对应的内存地址。

 

Ok,写到这里,结合第一次讲System.Object的那篇文章,我想,对一个Object的认识应该有个大体的比较清晰的架构了。

 

最后,做个广告^_^

欢迎志同道合的同志们加入:Share Source CLI核心技术探索团队,这里,有一群对DotNet世界最核心技术充满好奇和执着探索的朋友们,期待你充满智慧的文章和加盟。

 

2008-3-14 19:48:01

posted on 2008-03-14 19:55  lbq1221119  阅读(5191)  评论(13编辑  收藏  举报

导航