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. perl 处理文本

    redis01:/root# cat abc GET /api/sale/get_voucher_list?loupan_id=32300&suid=kJIjl&loupan_site ...

  2. 清华集训2014 day2 task1 简单回路

    题目 如题. 算法 就是刚学习的插头DP. 从前往后和从后往前分别进行一次DP. 要点 合法的括号序列只有103个 如何合并两次dp的信息 一开始犯傻了,以为当且仅当两个轮廓线的状态相同才是合法的方案 ...

  3. JS乘法口诀表(一行代码)

    (function(c){for(i=1;i<=9;i++){var s='';for(j=1;j<=i;j++){s+=i+'X'+j+'='+i*j+'\t';}c.debug(s); ...

  4. zepto.js介绍(持续更新)

    前言: zepto是一个简化版的jQuery,主要针对移动端开发. 简化了jQuery里很多的浏览器兼容性代码,jQuery的很多方法都被拿掉了(eg:slideUp). WP设备兼容性很差. 官方链 ...

  5. Java基础04 封装与接口

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 总结之前的内容,对象(object)指代某一事物,类(class)指代象的类型.对 ...

  6. Matlab,Visio等生成的图片的字体嵌入问题解决方法

    确保所有字体嵌入,是生成高质量学术论文的必要条件.但是在Windows下,总会遇到Matlab或Visio生成字体没有嵌入的问题,当然这个问题的解决办法有很多(例如,对于Visio可以这样做:直接拷贝 ...

  7. How to find variable is empty in shell script

    (1). var="" if [ -n "$var" ]; then     echo "not empty" else     echo ...

  8. PHP 页面跳转到另一个页面的几种方法分享

    如何在 PHP 中从一个页面重定向到另外一个页面呢?今天 清源 为大家列举出了三种办法,供大家来参考. 一.用HTTP头信息  也就是用PHP的HEADER函数.PHP里的HEADER函数的作用就是向 ...

  9. QT调用CURL

    QProcess *mProcess; QStringList arguments; arguments<<"--disable-epsv" <<" ...

  10. Opencv2系列学习笔记8(图像滤波)

    一:概念: 滤波是信号处理机图像处理中的一个基本操作.滤波去除图像中的噪声,提取感兴趣的特征,允许图像重采样. 图像中的频域和空域:空间域指用图像的灰度值来描述一幅图像:而频域指用图像灰度值的变化来描 ...