Delphi中类的运行期TypeInfo信息结构说明

CnPack 开源软件项目 2007-09-19 21:55:58

Delphi中类的运行期TypeInfo信息结构说明
作者:刘啸
CnPack开发组 http://www.cnpack.org
关键字:RTTI, TypeInfo, TypeData, PropInfo
(转载请注明出处并保持完整)

一、引子

Delphi运行期间,一个对象变量实际上是一个四字节指针,指向内存中此对象具体占据的一片区域,而区域的首个四字节又是一个指针指向该类的VMT,所有该类的实例对象的区域的首四字节指针都指向同一个VMT,故此一个VMT基本上就可以代表类本身。而每个类的VMT前面(VMT指针所指处的负偏移处)保存了该类的一些运行期信息,包括-44(vmtClassName)处的指向ClassName的字符串指针,-40(vmtInstanceSize)处的对象实例大小InstanceSize等。而本文专门讲述其-60(vmtTypeInfo)处的TypeInfo/ClassInfo指针所指的、本类的属性的RTTI信息。

二、TTypeInfo及其结构

TypInfo单元中声明的TTypeInfo结构描述了所有带RTTI的基本类型信息,而不光是针对类的。一个类的VMT首部偏移-60(vmtTypeInfo)处的四字节是一个TypeInfo/ClassInfo指针,指向一个TTypeInfo结构。
TTypeInfo在TypInfo中的定义与加的注释如下:

TTypeInfo = record
    Kind: TTypeKind; // 该类型信息所描述的类型,是类则为tkClass
    Name: ShortString; // 该类型信息所描述的类型名,是类时则为类名。
   {TypeData: TTypeData}
  end;
  
虽然看起来它定义得挺简单,只有两个成员,但它在运行期却是个巨大的复杂结构,因为它后面实际上紧接着一个TTypeData结构。TTypeData 结构是个大的共用体,对于类来说,它的定义和注释节选一段如下:

TTypeData = packed record
  ...
    case TTypeKind of
      tkClass: (
        ClassType: TClass;
        ParentInfo: PPTypeInfo; // 指向父类的 TypeInfo 结构
        PropCount: SmallInt;    // 本类的总属性数目,包括父类的属性数
        UnitName: ShortStringBase; // 本类所在的单元名
       {PropData: TPropData});  
  ...
  end;
         
这个结构除了这四个成员外,后面在运行期间跟着一个TPropData结构,这个结构则存储了所有属性的类型信息。TPropData的结构定义和注释如下:

TPropData = packed record
    PropCount: Word;  // 本类的属性数目,不包括父类
    PropList: record end;
    {PropList: array[1..PropCount] of TPropInfo}
  end;
  
它其中就一个PropCount,后面是个不定长的PropList的数组,每个元素是一个属性描述结构TPropInfo。
TPropInfo定义又如下:

PPropInfo = ^TPropInfo;
  TPropInfo = packed record
    PropType: PPTypeInfo;
    GetProc: Pointer;
    SetProc: Pointer;
    StoredProc: Pointer;
    Index: Integer;
    Default: Longint;
    NameIndex: SmallInt; 
    // NameIndex 是本属性在本类所有属性中的排名。
    // 一个类的所有直属属性的排名可能不是从0开始的,因为父类可能有属性。
    Name: ShortString;
  end;
  
这样,以上几个结构便嵌套而组成了一个类的巨大的属性信息,所有内容全是顺序排列,连ShortString都是。
需要说明的是,这儿所写的ShortString在实际场合并不是固定的长255,而是个可变长的字符串,第0个字节是长度,从字符串第一位开始跳过长度所指明的距离便到了下一个成员。这样的字符串紧凑结构有利于节省内存。

三、图示

以上介绍难免不够直观,这里用文本画一个图以指明它们的关系:

|---------|
                              |ClassInfo|---|
                              |---------|   |
Object Ref                    |---------|   |
|-------|                     | ...     |   |
|  Ref  |       Object        |---------|   |
|-------|----->|-------|0     |---------|   |
               |VMT Ptr|----->|---------|0  |
               |Field1 |      | VM 1    |   |
               |Field2 |      | VM 2    |   |
               |-------|      |---------|   |
                                            |
                                            |
|-------------------------------------------
|
|
|--->|TTypeInfo--------------------------|0
      |Kind: TTypeKind;                   |
      |Name: ShortString; // 不定长       |
      | |TTypeData------------------------|
      | |ClassType: TClass;               |
      | |ParentInfo: PPTypeInfo;          |// 指向父类的ClassInfo
      | |PropCount: SmallInt;             |
      | |UnitName: ShortStringBase;       |// 不定长
      | | |TPropData----------------------|
      | | |PropCount: Word;               |
      | | | |PropList(TPropInfo array)----|
      | | | | |1PropType: PPTypeInfo;     |
      | | | | |1GetProc: Pointer;         |
      | | | | |1SetProc: Pointer;         |
      | | | | |1StoredProc: Pointer;      |
      | | | | |1Index: Integer;           |
      | | | | |1Default: Longint;         |
      | | | | |1NameIndex: SmallInt;      |
      | | | | |1Name: ShortString;        |// 不定长
      | | | | |2PropType: PPTypeInfo;     |
      | | | | |2GetProc: Pointer;         |
      | | | | |2SetProc: Pointer;         |
      | | | | |2StoredProc: Pointer;      |
      | | | | |2Index: Integer;           |
      | | | | |2Default: Longint;         |
      | | | | |2NameIndex: SmallInt;      |
      | | | | |2Name: ShortString;        |
      | | | | |...                        |
      | | | | |...                        |

四、获取属性信息的系统函数分析

这里分析几个运行期获得类属性的RTTI信息的函数,以加深对本文的理解。

1.GetTypeData 从一个类的 TypeInfo/ClassInfo 指针得到一个类的 TypeData 指针。

function GetTypeData(TypeInfo: PTypeInfo): PTypeData; assembler;
asm
        { ->    EAX Pointer to type info }
        { <-    EAX Pointer to type data }
        {       it's really just to skip the kind and the name  }
        XOR     EDX,EDX
        MOV     DL,[EAX].TTypeInfo.Name.Byte[0]
        LEA     EAX,[EAX].TTypeInfo.Name[EDX+1]
end;

这个函数比较简单,就是从TTypeInfo中跳过Kind和Name,直接到TypeData的指针。代码中的注释也说明了这一点。

2. GetPropInfos

本函数将一个类的所有属性信息的地址转存到一个预先分配好的列表中,其内在机制稍微复杂一点,简而言之是遍历本类以及父类的属性数组并把遍历到的每一处的属性地址写入列表中。详见注释:

procedure GetPropInfos(TypeInfo: PTypeInfo; PropList: PPropList); assembler;
asm
        { ->    EAX Pointer to type info        }
        {       EDX Pointer to prop list        }
        { <-    nothing                         }

PUSH    EBX
        PUSH    ESI
        PUSH    EDI

XOR     ECX,ECX
        MOV     ESI,EAX // ESI 指向 TypeInfo
        MOV     CL,[EAX].TTypeInfo.Name.Byte[0]
        MOV     EDI,EDX
        XOR     EAX,EAX
        MOVZX   ECX,[ESI].TTypeInfo.Name[ECX+1].TTypeData.PropCount 
        // 跳过类型名,得到后面的TypeData
        REP     STOSD  
        // 根据本类的总属性数目(已经包括了父类了),将目的数组初始化填0

@outerLoop:
        MOV     CL,[ESI].TTypeInfo.Name.Byte[0] 
        // 跳过 Name 字符串长度
        LEA     ESI,[ESI].TTypeInfo.Name[ECX+1] 
        // ESI 得到一个类的TypeData,循环开始时是本类的TypeData,
        // 下一个循环时可能是父类的TypeData
        MOV     CL,[ESI].TTypeData.UnitName.Byte[0] 
        // 跳过UnitName字符串的长度
        MOVZX   EAX,[ESI].TTypeData.UnitName[ECX+1].TPropData.PropCount 
        // 得到本类的属性数目,不包括父类
        TEST    EAX,EAX
        JE      @parent // 如果本类无属性则跳到寻找父类处
        LEA     EDI,[ESI].TTypeData.UnitName[ECX+1].TPropData.PropList 
        // 准备写入PropList

@innerLoop: // 第一次进入时,EDI 指向 PropList中的第一个元素,此后 EDI 递增。

MOVZX   EBX,[EDI].TPropInfo.NameIndex 
        // EBX 获得 EDI 指向的属性的 Index
        MOV     CL,[EDI].TPropInfo.Name.Byte[0]
        CMP     dword ptr [EDX+EBX*4],0 
        // 查该PropList的Index位置上是否已经存了指针了。
        JNE     @alreadySet
        MOV     [EDX+EBX*4],EDI         // 没存过,则存

@alreadySet:
        LEA     EDI,[EDI].TPropInfo.Name[ECX+1] 
        // 跳过一个Name的ShortString,EDI便指向PropList中的下一个元素了。
        DEC     EAX
        JNE     @innerLoop

@parent:
        MOV     ESI,[ESI].TTypeData.ParentInfo 
        // 寻找父类的,如果有父类的信息,则 ESI 指向父类的 TypeInfo
        XOR     ECX,ECX
        TEST    ESI,ESI
        JE      @exit
        MOV     ESI,[ESI]
        JMP     @outerLoop
@exit:
        POP     EDI
        POP     ESI
        POP     EBX

end;

五、总结

本文是作者在写代码过程中的一些研究总结的结果,主要以D5/D7为准。其他版本IDE的VCL源码的相关部分和本文中的应该也没多大本质区别,欢迎一起讨论。

Delphi中类的运行期TypeInfo信息结构说明的更多相关文章

  1. Delphi中类的运行期TypeInfo信息结构说明(转载)

    Delphi中类的运行期TypeInfo信息结构说明作者:刘啸CnPack开发组 http://www.cnpack.org关键字:RTTI, TypeInfo, TypeData, PropInfo ...

  2. Access Violation分成两大类:运行期和设计期(很全的解释)

    用Delphi开发程序时,我们可以把遇到的Access Violation分成两大类:运行期和设计期. 一.设计期的Access Violation 1.硬件原因  在启动或关闭Delphi IDE以 ...

  3. C++学习笔记28:运行期型式信息

    RTTI 运行期标识对象的型式信息 优势:允许使用指向基类的指针或引用自如地操作派生类的对象 typeid:获取表达式的型式:type_info:型式信息类 头文件:typeinfo 对象转型模板 d ...

  4. C++编译期多态与运行期多态

    前言 今日的C++不再是个单纯的"带类的C"语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分.在面向对象C++编程中, ...

  5. Effective Objective-C 2.0 — 第二章 对象、消息、运行期 - 第六条:理解“属性”这一概念

    开发者通过对象来 存储并传递数据. 在对象之间传递数据并执行任务的过程就叫做“消息传递”. 这两条特性的工作原理? Objective-C运行期环境(Objective-C runtime) ,提供了 ...

  6. effective OC2.0 52阅读笔记(二 对象、消息、运行期)

    第二章:对象.消息.运行期 6 理解属性这一概念 总结:OC解决硬编码偏移量问题的做法,一种方案是把实例变量当做一种存储偏移量所用的特殊变量,交由类对象保管,偏移量会在运行期查找,叫做稳固的“应用程序 ...

  7. Spring事务管理只对出现运行期异常进行回滚

    原文:http://blog.csdn.net/abc19900828/article/details/39497631 使用spring难免要用到spring的事务管理,要用事务管理又会很自然的选择 ...

  8. JVM-程序编译与代码晚期(运行期)优化

    晚期(运行期)优化 1.为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time,JI ...

  9. atitit. hb 原生sql跨数据库解决原理 获得hb 数据库类型运行期获得Dialect

    atitit. hb 原生sql跨数据库解决原理 获得hb 数据库类型运行期获得Dialect   #-----原理 Hibernate 运行期获得Dialect   2010-07-28 12:59 ...

随机推荐

  1. 基于visual Studio2013解决C语言竞赛题之0904文件排序

       题目

  2. cmake手册详解----转

    参考链接:http://www.cnblogs.com/coderfenghc/tag/cmake/

  3. ORA-19815,ORA-19809 :limit exceeded for recovery files

    数据库重新启动的时候,收到了ORA-19815的错误.从错误的提示来看,是由于闪回区的空间被填满导致无法成功启动.这种情形我们通常考虑的是清除归档日志,那就直接在OS层面rm了,真的是这样吗?客官,如 ...

  4. STM32 + RT Thread OS 学习笔记[二]

    串口通讯例程 通过上面的练习,对STM32项目开发有了一个直观印象,接下来尝试对串口RS232进行操作. 1.   目标需求: 开机打开串口1,侦听上位机(使用电脑串口测试软件)发送的信息,然后原样输 ...

  5. Deamon Thread 讲解

    The daemon thread's life cycle is same with the life cycle of the application which starts this daem ...

  6. How to install vim on linux

    前几日了解到 vim 文本编辑器,据说很强大,使用起来效率很高,今天怀着很好奇的心理,学习了一下vim编辑器的下载安装方法,在此详细介绍一下安装vim编辑器的几种常用方法: 第一种方法就是在 Ubun ...

  7. C# 中 双问号??的用法

    int? x = null;int y = x ?? -1; 这里的y不能为null,但是等于x,x为null时赋值给y会报错.?? 可以在x==null时对y赋值-1 更多相关资料:https:// ...

  8. C++操作符的优先级

    C++操作符的优先级 C++操作符的优先级 操作符及其结合性 功能 用法 L L L :: :: :: 全局作用域 类作用域 名字空间作用域 ::name class::name namespace: ...

  9. CorePlot学习零---安装

    刚開始接触CorePlot时,网上搜到非常多相关文章,解说怎样安装这个第三方库,到眼下阶段该库的版本号已经到了1.5了,可是在github上你能够看到他的安装方法,只是为啥就没有codpod来安装呢? ...

  10. Swift - 短信发送功能的实现

    使用MessageUI.framework框架可以实现短信发送功能,步骤如下: (1)首先判断设备是否有发送短信功能 (2)如果设备允许发送短信,创建一个MFMessageComposeViewCon ...