Современные технологии программирования |
Конспект лекций |
Лекция 10. Средства для работы с RTTI
Источники дополнительных сведений *
Введение
Для каждого класса во время выполнения программы в памяти хранится информация о его типе – так называемая информация о типе времени выполнения (Run-Time Type Information, сокращённо RTTI).
Классовые методы и указатели на класс предназначены для организации доступа к этой информации.
Класс Tobject является предком всех классов Delphi. Описываемый вами класс будет наследовать от него, даже если вы явно это не указали в описании.
Класс TObject описан в модуле System следующим образом:
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
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;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
Назначение наиболее часто применяемых методов класса следующее:
Классовые методы – это методы, которые оперируют с RTTI класса, а не с объектами класса.
Возможность работы с RTTI каждый класс наследует от Tobject через соответствующие методы класса Tobject.
Для объявления метода классовым методом в объявлении типа используется зарезервированное слово class. Это слово помещается в левой части заголовка процедуры или функции, представляющей метод, перед словами function, procedure.
Это же слово class должно присутствовать и в описании классового метода.
В описании кода, выполняемого классовым методом недопустимо использование ссылок на поля объектов, так как классовому методу при вызове указатель на объект не передаётся.
Классовые методы вызываются двумя способами:
При вызове классового метода в него по умолчанию передаётся указатель на RTTI класса, который внутри кода метода именуется словом self.
Пример 1. Имя родителя
program PClassM;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
T1 = class
class function ParentName: String;
end;
T2 = class(T1)
end;
class function T1. ParentName: String;
begin
result:= self.ClassParent.ClassName;
end;
var
a: T1;
s: String;
begin
s:= T1.ParentName;//s = 'TObject'
writeln(s);
s:= T2.ParentName;//s = 'T1'
writeln(s);
a:= T1.Create;
s:= a.ParentName;//s = 'TObject'
writeln(s);
a.Free;
a:= T2.Create;
s:= a.ParentName;//s = 'T1'
writeln(s);
a.Free;
readln;
end.
Пример 2. Счётчик объектов
unit UCounter;
interface
type
//-----------------------------------------------------------------
T1 = class
class function P: integer; virtual;
constructor Create;
destructor Destroy; override;
end;
//-----------------------------------------------------------------
T2 = class(T1)
class function P: integer; override;
constructor Create;
destructor Destroy; override;
end;
implementation
//-----------------------------------------------------------------
var a:integer = 0;
var b:integer = 0;
//-----------------------------------------------------------------
class function T1.P: integer;
begin
result:= a;
end;
//-----------------------------------------------------------------
constructor T1.Create;
begin
inc(a)
end;
//-----------------------------------------------------------------
destructor T1.Destroy;
begin
dec(a)
end;
//-----------------------------------------------------------------
class function T2.P: integer;
begin
result:= b;
end;
//-----------------------------------------------------------------
constructor T2.Create;
begin
inc(b)
end;
//-----------------------------------------------------------------
destructor T2.Destroy;
begin
dec(b)
end;
end.
//-----------------------------------------------------------------
program PCounter;
{$APPTYPE CONSOLE}
uses
SysUtils,
UCounter in 'UCounter.pas';
var o: T1;
begin
writeln(T1.P);//0
writeln(T2.P);//0
o:= T1.Create;
writeln(o.P);//1
o:= T2.Create;
writeln(o.P);//1
o.Free;
writeln(T2.P);//0
writeln(T1.P);//1
readln;
end.
Для работы с RTTI класса в Object Pascal есть ещё один способ – с помощью указателей на классы, которые фактически являются указателями на RTTI. Рассмотрим следующие вопросы:
Указатель на класс можно описать для любого класса. Для этого в разделе описания типов необходимо связать имя типа указатель на класс с именем типа базового класса, на который он ссылается. Это выполняется с помощью зарезервированных слов class of (см. Пример 3. Предопределённые типы указателей на класс).
Множество значений типа данных указатель на класс – есть множество указателей (адресов) на RTTI базового класса.
Значение указателя на класс в тексте программы может быть представлено идентификатором базового класса (см. Пример 4. Указатель на класс).
Для библиотечных классов Delphi указатели на класс уже описаны (см. Пример 3).
Пример 3. Предопределённые типы указателей на класс
TClass = class of Tobject;
ExceptClass = class of Exception;
TComponentClass = class of TComponent;
TControlClass = class of TControl;
TFormClass = class of TForm;
Пример 4. Указатель на класс
Type
TMy = class;
TMyClassRef = class of TMy;
Var AClassRef: TMyClassRef;
AnObject: TMy;
Begin
AClassRef:= TMy;
AnObject:= AClassRef.Create;
Согласно правилу, совместимость по присваиванию для указателей на классы определена так: указателю на родительский класс можно присваивать указатели на классы-потомки. Но не наоборот.
Благодаря этому, посредством указателя на корневой класс Tobject, можно извлечь из RTTI имена любых используемых в примере классов.
Пример 5. Совместимость по присваиванию для указателей на класс
//-----------------------------------------------------------------
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
T1 = class end;
T2 = class(T1)end;
T1Ref = class of T1;
T2Ref = class of T2;
var PT : TClass;
PT1: T1Ref;
PT2: T2Ref;
begin
PT:= TObject;
writeln(PT.ClassName);//'TObject'
writeln(PT.InstanceSize);//4
PT:= T1;
writeln(PT.ClassName);//'T1'
writeln(PT.InstanceSize);//4
writeln(PT.ClassParent.ClassName);//'TObject'
PT:= T2;
writeln(PT.ClassName);//'T2'
writeln(PT.InstanceSize);//4
writeln(PT.ClassParent.ClassName);//'T1'
PT1:= T1;
writeln(PT1.ClassName);//'T1'
writeln(PT1.InstanceSize);//4
writeln(PT1.ClassParent.ClassName);//'TObject'
PT1:= T2;
writeln(PT1.ClassName);//'T2'
writeln(PT1.InstanceSize);//4
writeln(PT1.ClassParent.ClassName);//'T1'
PT2:= T2;
writeln(PT2.ClassName);//'T2'
writeln(PT2.InstanceSize);//4
writeln(PT2.ClassParent.ClassName);//'T1'
readln;
end.
//-----------------------------------------------------------------
Когда применяются указатели на класс? Указатели на класс позволяют оперировать данными класса (информацией о классе). Возможно использование указателя на класс в любом выражении, где разрешено использование такого типа данных. Таких выражений не так много, но несколько случаев представляют интерес.
Простейший случай – создание объекта.
Type
TMy = class;
TMyClassRef = class of TMy;
Var AClassRef: TMyClassRef;
AnObject: TMy;
Begin
AClassRef:= TMy;
AnObject:= AClassRef.Create;
Указатель TFormClass используется в исходном коде большинства проектов Delphi.
Application.FormCreate(TForm1, Form1);
Метод Formcreate объекта TApplication требует в качестве параметра указатель на класс формы, которую нужно создать. Первый параметр – указатель на класс, второй- переменная, хранящая указатель на созданный экземпляр объекта.
Ссылку на класс можно использовать для вызова классовых методов связанного с ней класса.
writeln(AClassRef.InstantSize);
writeln(AClassRef.ClassName);
writeln(AClassRef.InheritsFrom);
writeln(AClassRef.ClassParent.ClassName);
Пример 6. Динамическое создание компонента
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, stdctrls,
ExtCtrls;
type
TForm1 = class(TForm)
RadioGroup1: TRadioGroup;
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormCreate(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
ClRef: TControlClass = TEdit;
Counter: Integer = 5;//Счётчик числа созданных компонентов
implementation
{$R *.DFM}
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
MyName: String;//Имя созданного компонента
begin
with ClRef.Create(self) do begin;
Parent:= Self;
Left:= X;
Top:= Y;
Width:= Width + 30;
inc(Counter);
MyName:= ClassName + IntToStr(Counter);
Delete(MyName,1,1);
Name:= MyName;
Caption:= Format('Control %s at %d, %d',[ClassName,X,Y]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Counter:= 5;
RadioGroup1.ItemIndex:= 0;
ClRef:= TEdit;
end;
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
case RadioGroup1.ItemIndex of
0: ClRef:= TEdit;
1: ClRef:= TRadioButton;
2: ClRef:= TCheckBox;
end;
end;
end.
Рис. Главная форма приложения для динамического создания компонентов.
Приложение, главная форма которого приведена на рисунке, работает следующим образом.
Пример 7. Динамическое создание компонента
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
CheckBox1: TCheckBox;
procedure MouseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Counter: integer = 5;
implementation
{$R *.dfm}
procedure TForm1.MouseClick(Sender: TObject);
var ref: TControlClass;
begin
ref:= TControlClass(sender.ClassType);
with ref.Create(self) do begin
Parent:= self;
top:= (sender as TControl).Top + Counter*4;
Left:= (sender as TControl).Left + Counter*4;
Name:= 'Comp'+ intTostr(Counter);
inc(Counter);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.OnClick:= MouseClick;
Edit1.OnClick:= MouseClick;
CheckBox1.OnClick:= MouseClick;
end;
end.
Источники дополнительных сведений