网上已经有很多关于RTTI的博客,最近刚好看到这里,以前没弄懂的东西,这次一起搞明白一下,写个博客,算是做个笔记。

这里有一篇英文文档,说的很详细:
Delphi Q&A

概念

每个Delphi的类都有一张虚拟方法表(virtual-method table),或者说,Delphi的类是由它来定义的。从编译器角度来看,一个类就是指向VMT的指针。

一个虚拟方法表从指针所指地址的负偏移76 处开始,长度动态分配(由虚拟方法的个数确定)。虚拟方法表被分为很多小段,每段占4 个字节,也就是一系列指针的列表。每个指针指向一个虚拟方法的入口地址。

一个VMT包含

  1. 基础信息区

VMT负偏移区(-76-0)即为基础信息区。存储了基础数据(如实例大小)、基础数据的指针(如接口表、运行时类型信息表、字段表、方法表、类名和父类虚拟方法表等)和所有基础性虚拟方法的指针。这个区域的数据和指针帮助实现对象的构造和析构、运行时类型信息存取、字段和方法解析等。大小是固定的。

  1. 用户定义虚拟方法区

VMT正偏移区即为用户定义虚拟方法(即所有非Object定义的虚拟方法)区。每4个字节存储一个用户定义的虚拟方法指针。这些虚拟方法包括本类中定义的虚拟方法以及从TObject一直到本类的所有中间类定义的所有虚拟方法。

这些内容,在编译的时候就已经被确认了,VMT最重要的用途在于,保存了类的虚方法的指针。

值得注意的是
类的方法分为两种:对象级别的方法和类方法,两者的self指针意义不同。对象的self指针指向对象的地址空间,只能访问对象的成员函数。而类的self,指向类的VMT,可以访问VMT中的信息。

VMT

请看官方文档提供的例子:

varObjList1, ObjList2: TList;
beginObjList1 := TObjectList.Create(True);ObjList2 := TObjectList.Create(True);
end;

两个TList对象由子类给创建,下图给出了VMT详情:

对象的地址的前4字节是该类的VMT,VMT中又包含了父类VMT信息的引用。

详细介绍

所有的类都继承与TObject,具有TObject的所有特性。

TObject类中有这样两个方法:

1.ClassType,获取类的VMT信息

function TObject.ClassType: TClass;
beginPointer(Result) := PPointer(Self)^;
end;

可以看到,ClassType方法是对象的方法,self指向的是对象的地址空间,因此,取self的地址前4字节的内容刚好是类的VMT表。如:

//随意创建一个空项目
varForm1: TForm1;
...function test(): TClass;
beginresult := Form1.ClassType;        //TForm1
end;...

2.ClassName,获取类名信息

class function TObject.ClassName: ShortString;
beginResult := PShortString(PPointer(Integer(Self) + vmtClassName)^)^;
end;

这个类加上了class前缀,表示该方法是类方法,即使用类名直接调用, self指向的是VMT。代码意思是:Integer(self)取到self的地址,然后加上vmtClassName偏移,然后用shortString指针指向它,然后取到该地址里放的类名。

下面列出了VMT表中部分信息的负偏移的定义:

constvmtSelfPtr           = -76;vmtIntfTable         = -72;vmtAutoTable         = -68;vmtInitTable         = -64;vmtTypeInfo          = -60;vmtFieldTable        = -56;vmtMethodTable       = -52;vmtDynamicTable      = -48;vmtClassName         = -44;vmtInstanceSize      = -40;vmtParent            = -36;

3.Delphi还提供了几个函数获取RTTI。

//在System.pas单元内
TObject = class
...class function ClassNameIs(const Name: string): Boolean;class function ClassParent: TClass;class function ClassInfo: Pointer;class function InstanceSize: Longint;class function InheritsFrom(AClass: TClass): Boolean;class function MethodAddress(const Name: ShortString): Pointer;class function MethodName(Address: Pointer): ShortString;...
end

RTTI详情

结构如下(TypInfo.pas单元中):

  PPTypeInfo = ^PTypeInfo;PTypeInfo = ^TTypeInfo;TTypeInfo = recordKind: TTypeKind;Name: ShortString;{TypeData: TTypeData}end;

TObjectt提供了类方法获取该结构的信息:

class function TObject.ClassInfo: Pointer;
beginResult := PPointer(Integer(Self) + vmtTypeInfo)^;
end;

其中,TTypeKind类型,枚举了所有的RTTI信息的数据类型:

  TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);

1.获取类的属性信息
修改于网上的代码:

unit main;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, TypInfo;type
{$M+}TBaseType = classpublicData: Variant;end;TMyType = classMyDefault: TBaseType;privateMyPublished: TBaseType;FID : Integer;protectedFMyProtected: TBaseType;publicMyPulbic: Variant;publishedproperty MyPublish: TBaseType read MyPublished write MyPublished;property MyProtected: TBaseType read FMyProtected write FMyProtected;property ID : Integer read FID write FID;end;{$M-}TForm2 = class(TForm)mmo1: TMemo;btn1: TButton;procedure btn1Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end;procedure Visit(aList: TStrings);varForm2: TForm2;implementation{$R *.dfm}procedure Visit(aList: TStrings);
varlTypeInfo: PTypeInfo;lTypeData: PTypeData;lPropList: PPropList;lcount: integer;i: integer;lkind, lname: string;
beginlTypeInfo := TMyType.ClassInfo;       //获取RTTI结构lTypeData := GetTypeData(lTypeInfo);lcount := lTypeData.PropCount;aList.Add(TMyType.ClassName);GetMem(lPropList, SizeOf(TPropInfo) * lcount);tryGetPropInfos(lTypeInfo, lPropList);         //获取属性列表for i := 0 to lcount - 1 dobeginlkind := InttoStr(Ord(lPropList[i]^.PropType^.Kind));lname := lPropList[i]^.PropType^.Name;aList.Add('kind:' + lkind + ' name:' + lname)end;finallyFreeMem(lPropList);     //记得释放数组的内容end;
end;procedure TForm2.btn1Click(Sender: TObject);
varlt: Tstringlist;i: integer;
beginlt := Tstringlist.Create;tryvisit(lt);mmo1.Lines.Clear;mmo1.Lines.AddStrings(lt);finallylt.Free;end;
end;end.//输出内容:
//TMyType
//kind:7 name:TBaseType
//kind:7 name:TBaseType
//kind:1 name:Integer

其中,TTypeData是个变体结构:

  PTypeData = ^TTypeData;TTypeData = packed record...tkClass: (ClassType: TClass;ParentInfo: PPTypeInfo;PropCount: SmallInt;UnitName: ShortStringBase;{PropData: TPropData});      //属性信息...end

TPropData结构如下:

  TPropData = packed recordPropCount: Word;     //属性个数PropList: record end;{PropList: array[1..PropCount] of TPropInfo}     //属性列表end;

TPropInfo结构如下:

  PPropInfo = ^TPropInfo;TPropInfo = packed recordPropType: PPTypeInfo;GetProc: Pointer;   //属性的get方法SetProc: Pointer;     //属性的set方法StoredProc: Pointer;  //与属性stored关键字相关Index: Integer;     //属性的index值Default: Longint;    //属性的default值NameIndex: SmallInt;Name: ShortString; //属性名end;

使用GetPropInfos方法获取属性列表

procedure GetPropInfos(TypeInfo: PTypeInfo; PropList: PPropList); assembler;

2.获取方法的信息
直接上代码:
注释都写在代码中了,对照着数据的结构看会清晰很多,入口在TForm2.btn2Click中。

unit main;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, TypInfo;type
{$M+}TBaseType = classpublicData: Variant;end;TMyType = classMyDefault: TBaseType;privateMyPublished: TBaseType;FID: Integer;protectedFMyProtected: TBaseType;publicMyPulbic: Variant;publishedproperty MyPublish: TBaseType read MyPublished write MyPublished;property MyProtected: TBaseType read FMyProtected write FMyProtected;property ID: Integer read FID write FID;end;{$M-}TForm2 = class(TForm)mmo1: TMemo;btn2: TButton;procedure btn2Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end;type//ParamList: array[1..ParamCount] of TParamData//TypIinfo.pas line:200PParamData = ^TParamData;TParamData = recordFlags: TParamFlags;ParamName: ShortString;TypeName: ShortString;end;TMyMethod = function(a: array of char; var b: TObject): Boolean of object;procedure GetMethodInfo(aTypeInfo: PTypeInfo; aList: TStrings);varForm2: TForm2;implementation{$R *.dfm}//根据给定的枚举,获得对应的枚举名称
function GetParamFlagsName(aParamFlags: TParamFlags): string;
vari: Integer;
beginResult := '';for i := 0 to Ord(High(TParamFlag)) dobeginif i = Integer(pfAddress) then Continue;if TParamFlag(i) in aParamFlags thenResult := Result + ' - ' + GetEnumName(TypeInfo(TParamFlag), i);end;
end;procedure GetMethodInfo(aTypeInfo: PTypeInfo; aList: TStrings);
varlTypeData: PTypeData;lParamCount: Integer;lpTypeStr : PShortString;lParamData : PParamData;i: Integer;
beginlTypeData := GetTypeData(aTypeInfo);      //获得运行时信息lParamCount := lTypeData^.ParamCount; //参数个数  aList.Add('name-->' + aTypeInfo^.Name);       //方法名aList.Add('kind-->' + GetEnumName(TypeInfo(TMethodKind), Integer(lTypeData^.MethodKind)));   //方法类型aList.Add('method count-->' + IntToStr(lParamCount));   aList.Add('method data list -->');lParamData := PParamData(@lTypeData^.ParamList);       //参数列表:包含参数的修饰符(如var,out),参数名称,参数类型for i := 0 to lParamCount - 1 dobegin//每次长度不确定,需要使用指针步进方式取内容lpTypeStr := Pointer(Integer(@lParamData^.ParamName) + Length(lParamData^.ParamName) + 1);aList.Add(Format('%s - %s : %s', [GetParamFlagsName(lParamData^.Flags), lParamData^.ParamName, lpTypeStr^]));//移到下一个datalParamData := PParamData(Integer(lParamData) + SizeOf(TParamFlags) +Length(lParamData^.ParamName) + Length(lpTypeStr^) + 2);end;aList.Add('--------------------');
end;procedure TForm2.btn2Click(Sender: TObject);
varlt : TStringList;
beginlt := TStringList.Create;tryGetMethodInfo(TypeInfo(TMyMethod), lt);       //将对应方法的typeInfo传进去GetMethodInfo(TypeInfo(TMouseEvent), lt);mmo1.Lines.Clear;mmo1.Lines.AddStrings(lt);finallylt.Free;end;
end;end.

输出结果如图:

结束语

亲身体会,在一个几十万行代码的项目中,每次修改东西都要找很久才能找到对应的类和方法。为了防止盲目的寻找,可以在交互界面上,在鼠标事件加上输出对应Sender的RTTI信息(如:点击某个按钮时,输出某个按钮的所属的类名,点击事件的方法名),这样寻找起来,一键定位,特别方便。

参考

http://pages.cs.wisc.edu/~rkennedy/vmt#vmtInstanceSize
Delphi RTTI浅析

Delphi的RTTIVMT相关推荐

  1. Delphi 之Copyrect的使用

    http://cqujsjcyj.iteye.com/blog/380970 Copyrect的使用(图片复制.放大.以及做图片放大镜等) 一.从一个选取一个区域中的图象到另一个图象组件中的固定区域 ...

  2. delphi 10 seattle 中 解决IOS 9 限制使用HTTP 服务问题

    IOS 9 于17号早上正式开始推送,早上起来立马安装,这次升级包只有1G, 安装空间也大大降低(想起IOS 8 升级时,几乎把手机里面的东西删光了,满眼都是泪). 虽然安装后,网上几乎是铺天盖地的吐 ...

  3. 奇淫怪巧之给Delphi的PrintDialog增加一个页码选定范围打印的Edit

    在Delphi中使用PrintDialog打印对话框的时候,这个控件有三个选项,就是PrintRang那个属性的三个选项,其中有一个选项三,让我们自定义选择页码范围来打印.但是比较蛋疼的是,这个地方选 ...

  4. DELPHI 中 Window 消息大全使用详解

    Window 消息大全使用详解 导读: Delphi是Borland公司的一种面向对象的可视化软件开发工具. Delphi集中了Visual C++和Visual Basic两者的优点:容易上手.功能 ...

  5. [原]SSL 开发简述(Delphi)

    一.            简介 现在网上有关SSL的资料较多的是基于VC开发,Delphi的SSL开发资源很少. 本文主要使用OpenSSL为基础,讲述SSL的有关开发流程.OpenSSL功能非常丰 ...

  6. 初学 Delphi 嵌入汇编[3] - 第一个 Delphi 与汇编的例子

    前面知道了一个汇编的赋值指令(MOV), 再了解一个加法指令(ADD), 就可以做个例子了. 譬如: ADD AX,BX; 这相当于 Delphi 中的 AX := AX + BX; 另外提前来个列表 ...

  7. ado控件连接oracle,在Delphi 7中用ADOConnection控件连接Oracle 9i的问题

    我在本地机器上安装了oracle客户端,其中tnsnames.ora文件中的内容如下: dbtest_212.113.74.23 = (DESCRIPTION = (ADDRESS_LIST = (A ...

  8. delphi自定义事件处理

    http://www.cnblogs.com/ywangzi/archive/2012/09/06/2673414.html delphi自定义事件处理 为什么我们点击按钮,就会执行按钮的onclic ...

  9. 【delphi】Byte数组与String类型的转换

    string string = AnsiString = 长字符串,理论上长度不受限制,但其实受限于最大寻址范围2的32次方=4G字节: 变量Str名字是一个指针,指向位于堆内存的字符序列,字符序列起 ...

最新文章

  1. java初始化数据报_初始化java原因
  2. 别再用那些已经淘汰的技术了!2020年9大顶级Java框架出炉!!
  3. 王者荣耀活动精选 Blink 第二弹来袭!
  4. 机房空调制冷机柜起到了什么作用?
  5. java中ArrayList与LinkedList的区别
  6. 计算机基础及msoffice应用好考吗,全国计算机等级考试考试一级WPS Office和MS Office有什么不同?那个好考?...
  7. kubenetes中port、targetPort、nodePort、containerPort的区别与联系
  8. 取重复记录最大的id列表
  9. appium更新到1.8.2,不能打开运行的解决办法
  10. Python enumerate() 函数
  11. udp发包工具_利用nginx的第四层协议stream模块实现UDP端口的负载均衡
  12. 带你入门SpringCloud服务发现 | Eurka搭建和使用
  13. Codeforces Round #327 (Div. 2) C Median Smoothing(找规律)
  14. QQ2011的DD包密码验证报文解密密钥计算困惑之二
  15. CMake 安装升级更高版本
  16. STC - 非标连接的7段数码管赋值
  17. 云栖科技评论第18期:Tenable 发布全球安全指数
  18. 清华计算机系分数线2018四川,四川多少分能上清华?附清华大学在四川的录取分数线...
  19. 2143.replace.favo.xrcch.com Dns劫持解决方案
  20. 区块链游戏平台Gala Games能否重振链游?

热门文章

  1. 西安交通大学计算机组成原理实验,西安交通大学计算机组成原理专题实验(上)第一次实验报告.pdf...
  2. java excel合并,Java Excel合并工具
  3. 读《楚汉传奇》中历史故事悟项目管理
  4. 编写一个制造各种车辆的程序。包含三个类,具体要求如下: (1)基类Vehicle,包含轮子数和汽车自身重量两个属性,一个两参数的构造方法,一个显示汽车信息的方法; (2)小轿车类Car,增加载客数属性
  5. 基于C++的Qt(三)Qt类库概述
  6. 【淘宝经验分享】新开店铺如何提升流量
  7. html5调用腾讯视频,小程序h5获取腾讯视频的真实mp4地址video!【前端+后端方法】...
  8. 条件求和:SUMIF、SUMIFS函数
  9. 【哈工大SCIR笔记】机器阅读理解简述
  10. C语言实现物品竞拍管理系统