运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例
一、理解RTTI
先看看官方文档:http://docwiki.embarcadero.com/RADStudio/Rio/en/Working_with_RTTI 译文如下:
运行时类型信息(RTTI)是一种编程模式,其中可以在运行时获取有关类型的信息。如果启用了RTTI生成,则生成的二进制文件将包含特殊的元数据,该元数据包含有关类型的信息(例如,类的祖先class ancestry,声明的字段declared fields,带注释的属性annotated attributes【我加的:包含类的字段、属性、函数、过程,包括已发布的,公共的,受保护的,私有的】)。使用System.Rtti单元中提供的功能,您可以在运行时获取这些信息。最终结果是能够创建抽象和通用的框架,在其中可以操作任何公开的RTTI类型。
注意:RTTI是Delphi开发工具的特有的 。
注意:不会为泛型方法生成运行时类型信息。
我加的:Delphi将RTTI信息根植到编译后链接的包信息(链接产生的二进制文件,它被链接到.exe可执行文件中( Windows)、.jar( Android)、.静态文件.a.h( IOS))表中System.PackageInfoTable,运行时是去读取该表。注意:在iOS平台上,链接是静态完成的,因此不会植根RTTI。但是,如果使用GetType方法,则可以获取RTTI
RTTI生成的控制
使用以下编译器指令来控制运行时类型信息的生成。您可能需要限制RTTI的生成以减小可执行文件的大小。
Delphi | C ++ |
---|---|
{$ M},{$ TYPEINFO} | __declspec(delphirtti) |
{$ METHODINFO} | 不适用 |
{$ RTTI} | #pragmaexplicit_rtti |
{$ WEAKLINKRTTI} | 不适用 |
话题
- 获取RTTI上下文 Obtaining the RTTI Context
- 查询类型信息 Querying for Type Information
- 通用的类型信息 General Type Information
- 简单类型的信息 Information for Simple Types
- 结构化类型的信息 Information for Structured Types
- 类型上的运行时操作 Run-Time Operations on Types
- Delphi RTTI和C ++ Builder Delphi的RTTI and C++Builder
也可以看看
- Delphi中的属性 Attributes in Delphi
程式码范例
- 类目:RTTI Category
- RTL.AttributesAndRTTI示例(许多代码示例使用RTTI)
二、感性认知,先看看案例:
1、官网案例:http://docwiki.embarcadero.com/CodeExamples/Rio/en/Rtti.TRttiType_(Delphi)
2、官方安装包案例:C:\Users\Public\Documents\Embarcadero\Studio\20.0\Samples\Object Pascal\RTL\AttributesAndRTTI\rtti_browser\rtti_browser.dproj
3、我写给大家的案例,查询系统任何类和任何低级别的对象的定义(方法、属性(方法的读写产生属性信息)、字段(一般是私有的泛型,帮助属性写赋值和读信息)),适用于VCL和Fmx,可跨平台使用:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants,
System.Classes, System.IOUtils,
Vcl.Graphics,
Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
btn_getRttiTypeOfTComponent: TButton;
Panel1: TPanel;
Panel2: TPanel;
ComboBoxEx1: TComboBoxEx;
ComboBoxEx2: TComboBoxEx;
///<summary>按钮点击事件btn_getRttiTypeOfTComponent:</summary>
procedure btn_getRttiTypeOfTComponentClick(Sender: TObject);
///<summary>ComboBoxEx1.Items获取RTTIType所请求列表的TComponent的类型名</summary>
procedure ComboBoxEx1DropDown(Sender: TObject);
///<summary>产生文件及其路径:</summary>
procedure FormCreate(Sender: TObject);
private
LFilePath:string;
LFileHandle:THandle;
///<summary>Rtti获取delphi系统所有对象运行时的类名和元信息数组: </summary>
///<summary>1、TRttiContext上下文运行环境获取所有对象的运行时类型TRTTIType</summary>
///<summary>2、并计算某个TComponent的类名对应的类</summary>
///<summary>3、并获取某个类对象RTTIType的ClassName、QualifiedName</summary>
///<summary>4、并按类型ClassType的ClassName、方法Methods、属性Properties、字段Fields顺序</summary>
///<summary>:即TRttiType.AsInstance.MetaClassType.ClassName、TRttiMethod、TRttiProperty、TRttiField顺序</summary>
///<summary>:获取它们的元信息数组:</summary>
///<summary>:TRttiType.GetMethods;TRttiType.GetProperties;TRttiType.GetFields:</summary>
///<summary>5、通过按钮事件将上述信息写入TreeView(btn_getRttiTypeOfTComponentClick)</summary>
procedure getRttiTypeOfTComponent(const AComponentName:string);
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
RTTI;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LFilePath:=ExtractFilePath(ParamStr(0))+'VclTWinControlCategories.text';
if not System.SysUtils.FileExists(LFilePath) then
begin
LFileHandle:=System.SysUtils.FileCreate(LFilePath,fmShareDenyNone);
if System.SysUtils.FileExists(LFilePath) then
System.SysUtils.FileClose(LFileHandle);
//System.SysUtils.FileCreate System.IOUtils
end;
end;
procedure TForm1.ComboBoxEx1DropDown(Sender: TObject);
//:ComboBoxEx1.Items获取RTTIType所请求列表的TComponent的类型名
var
LFileLines:TArray<system.string>;
LTComponentClassName:string;
LListedAClassName:string;
LIfExistListedAClassName:Boolean;
begin
if (ComboBoxEx2.Items.Count<=1)
and (ComboBoxEx1.Items.Count<=1) then
begin
try
btn_getRttiTypeOfTComponentClick(sender);//:即:getRttiTypeOfTComponent(trim(ComboBoxEx1.Text));
//:执行完之后会自动返回到这里,继续往下执行
//:因为执行的是Rtti,(Sender:TObject)本身也是1个对象的Rtti
//:TNotifyEvent = procedure(Sender: TObject) of object;
//:TObject列不列出来,仅仅取决于其中执行的FillVclClasses中方法的定义
//:先执行Rtti,再执行sender
finally
end;
end;
if ComboBoxEx2.Items.Count>0 then
begin
LFileLines:=TFile.ReadAllLines(LFilePath);
if length(LFileLines)>0 then
begin
LIfExistListedAClassName:=false;
for LTComponentClassName in LFileLines do
begin
for LListedAClassName in ComboBoxEx1.Items do
if LListedAClassName=LTComponentClassName then
begin
LIfExistListedAClassName:=true;
Break;
end;
if LIfExistListedAClassName=false then
ComboBoxEx1.Items.Add(LTComponentClassName);
end;
end else exit;
end;
end;
procedure TForm1.btn_getRttiTypeOfTComponentClick(Sender: TObject);
begin
getRttiTypeOfTComponent(trim(ComboBoxEx1.Text));
end;
procedure TForm1.getRttiTypeOfTComponent(const AComponentName:string);
///<summary>返回是否成功,回调所有组件的TRttiType的实例TRttiInstanceType的元类的类名:</summary>
function FillVclClasses(ATStrings:TStrings): Boolean;
var
ctx: TRttiContext;
typ: TRttiType;
list: TArray<TRttiType>;
begin
Result:=false;
ctx := TRttiContext.Create;
list := ctx.GetTypes;
for typ in list do
begin
if typ.IsInstance //:是实例化的类型
//and typ.IsManaged //:不能用:源代码意思好像是指的基本的数据类型或tkClass被UI引用:TypeInfo^.Kind
//and typ.IsPublicType //:是公开的类型
and (
typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.StdCtrls')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ComCtrls')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ExtCtrls')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ActnList')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ImgList')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Graphics')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Menus')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Controls')
or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('TObject')
//or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('System.Classes')
//or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Themes') //:含枚举、集合、泛型列表和泛型字典
//or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Forms') //:不能获取TRttiType
)
//and not Uppercase(typ.AsInstance.MetaClassType.ClassName).Contains('TCUSTOM')
//and not typ.AsInstance.MetaClassType.ClassParent.QualifiedClassName.Contains('Vcl.Controls')
then
begin
ATStrings.Add(typ.AsInstance.MetaClassType.ClassName+sLineBreak);
end
else
begin
continue;
end
;
end;
ctx.Free;
if ATStrings.Count=0 then Result:=false
else Result:=true;
end;
///<summary>根据组件名或对象名返回对象的元类</summary>
function FindAnyClass(const Name: string): TClass;
var
ctx: TRttiContext;
typ: TRttiType;
list: TArray<TRttiType>;
begin
Result := nil;
ctx := TRttiContext.Create;
list := ctx.GetTypes;
for typ in list do
begin
if typ.IsInstance //:是实例化的类型
//and typ.IsManaged //:不能用:源代码意思好像是指的基本的数据类型或tkClass被UI引用:TypeInfo^.Kind
//and typ.IsPublicType //:是公开的类型
and SameStr( Uppercase(Name),Uppercase(typ.Name) )
and (Uppercase(Name)<>'TFRAME')//:相当于: and (typ.ClassName<>TFRAME.ClassName)
then
begin
Result := typ.AsInstance.MetaClassType;
break;
end
else
begin
Result := nil;
continue;
end;
end;
ctx.Free;
end;
var
//ATComponent:TComponent;
LContext: TRttiContext;
LType: TRttiType;
LMethod: TRttiMethod;
LProperty: TRttiProperty;
LField: TRttiField;
LTreeNode1, LTreeNode2: TTreeNode;
LLTypeName:string;
LStringList:TStrings;
LTreeNodeCount:Integer;
LIfExistTreeNode:Boolean;
LTClass:TClass;
LTComponentsList:string;
LTComponentsListCount:Integer;
begin
ComboBoxEx2.Items.Clear;
ComboBoxEx2.Items.BeginUpdate;
LStringList:=TStringList.Create;
try
if FillVclClasses(LStringList)=true then
for LLTypeName in LStringList do
begin
ComboBoxEx2.Items.Add(LLTypeName);
end;
finally
LStringList.Free;
end;
ComboBoxEx2.Items.EndUpdate;
LTComponentsList:='';
for LTComponentsListCount :=0 to ComboBoxEx2.Items.Count-1 do
begin
LTComponentsList :=LTComponentsList
+ComboBoxEx2.Items[LTComponentsListCount];
//:ComboBoxEx2.Items本身含换行分割符:
//:这样需加换行分割符:for LTComponent in ComboBoxEx2.Items do
end;
TFile.WriteAllText(LFilePath,LTComponentsList);
//ATComponent :=FindGlobalComponent( AComponentName );
//:不行:LTClass :=ATComponent.ClassType;
LContext := TRttiContext.Create;
try
LTClass :=FindAnyClass( AComponentName );
//:不行:ATComponent.ClassType;
if LTClass=nil then exit;
LType := LContext.GetType( LTClass );//as TWinControl
LIfExistTreeNode:=false;
for LTreeNodeCount := 0 to TreeView1.Items.Count-1 do
if (TreeView1.Items[LTreeNodeCount].Text =LType.ToString) then
LIfExistTreeNode:=true; //:已经存在了
if LIfExistTreeNode=false then //:不要重复加入TreeView1
begin
LTreeNode1 := TreeView1.Items.AddChild(nil, LType.ToString);
LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Methods');
for LMethod in LType.GetMethods do
begin
TreeView1.Items.AddChild(LTreeNode2, LMethod.ToString);
end;
LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Properties');
for LProperty in LType.GetProperties do
begin
TreeView1.Items.AddChild(LTreeNode2, LProperty.ToString);
end;
LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Fields');
for LField in LType.GetFields do
begin
TreeView1.Items.AddChild(LTreeNode2, LField.ToString);
end;
end;
//TreeView1.FullExpand;
finally
LContext.Free;
end;
end;
end.
program 获取某个TComponent类或TObject类的RttiType信息;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
运行效果:
VclTWinControlCategories.text 生成的信息:
三、实际应用中的作用
我们通常的简单代码,会少了一层,即对象池这一层。造成我们从服务器获取数据库中的信息后,客户端必须将其加载到本地的数据集(比如内存表TFDMemTable),再从内存表中将字段类型读取出来,与UI界面中用户的输入做比对,借此来达到用户输入的类型是否满足要求的目的。
这是很滞后的、很繁琐的、计算耗时效率较低、数据库服务器负荷重、系统内的对象是不能满足序列化和Json数据交换要求的,也是不好管理的,而且代码不易接口化扩展、不易复用,最终你的模式是很难真正实现Restful的。
1、输入的数据,类型及长度的有效性验证
从服务器获取对象信息后,客户端UI界面的输入信息,匹配设计要求,而此时客户端并不与数据库产生直接的联系。
2、登录信息匹配验证用户注册信息
方法与上雷同,只是多了与用户注册信息窗口中的相关信息的比对。
3、用户界面配置信息(即皮肤)的个性化、接口化
可根据用户选择的喜好,实现全系统快速换样式、换颜色、换字体等,而这一切都不需要修改UI部分代码,直接切换参数调用接口单元即可。