![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Список полей класса. Можно как то получить в runtime?
Begin © (2005-06-27 17:14:00)GetPropList(self.ClassInfo... и т.д. не сработал. Вернее, сработал, но только для получения published-свойств... А можно ли как то получить список тех полей, которые не-published ???
Вот к примеру :
TSomeClass = class
field1 : integer; //Вот эти поля сильно охота
field2 : integer; //получить в runtime
published
property prop1; // это вот GetPropList'ом возвращется,
// только надо не его...
end;
Спасибо заранее...
Erik1 (2005-06-28 15:35:00) [1]А теперь по теме вопроса: Наверное тебе стоит переспотреть подход к задаче. Масив с указателями на конструкторы объектов тебе уже предлагали. Необходимо заметить, что такой масив может содержать записи, а в них возможно задать стринговое поле с имененм класса. Возможны и другие варианты, опиши более деталь, что ты хочеш сделать и как это работает. Наверника есть более красивый способ.
Begin (2005-06-28 09:28:00) [2]О как ветка разраслась то... :)
Спасибо всем огромное, будем пробовать ! :)
default (2005-06-28 08:54:00) [3]Юрий Зотов © (28.06.05 08:53) [53]
offtopic: программистам была команда "не спать!" ?:)
Юрий Зотов (2005-06-28 08:53:00) [4]Надо уточнить: кидать на форму TEdit, TButton и TListBox надо строго в этой последовательности - к ней привязан метод Check.
evvcom (2005-06-28 08:38:00) [5]Во дают! Чего на работу никому не надо?
> Не перевелись хакеры на земле Русской :)
Хакер был, хакер есть, хакер не может не есть! (с) был про Ленина. :)
Просто Джо (2005-06-28 04:56:00) [6]Не перевелись хакеры на земле Русской :)
Юрий Зотов (2005-06-28 04:19:00) [7]Наверное, подводя итоги...
Накидал я тут по материалам этой ветки модулек, который может быть полезен. Экспортируются несколько типов и 2 функции. Одна дает адрес таблицы полей, другая перечисляет эти поля, вызывая callback (перечисление заканчивается, если callback вернет False). В итоге для любого класса можем получить все сведения о его published-полях, которые ссылаются на потомки TPersistent (имя, класс, смещение). Делал и тестировал в D7, но похоже, что код должен работать в любой версии Delphi.
unit FldInfo;
interface
uses
Classes;
type
PFieldClassTable = ^TFieldClassTable;
TFieldClassTable = packed record
Count: SmallInt;
Classes: packed array[0..8191] of ^TPersistentClass;
end;
PFieldRec = ^TFieldRec;
TFieldRec = packed record
Offset: integer;
ClassIndex: SmallInt;
Name: ShortString
end;
PFieldTable = ^TFieldTable;
TFieldTable = packed record
Count: SmallInt;
ClassTable: PFieldClassTable;
Fields: packed array[0..0] of TFieldRec
end;
PFieldInfo = ^TFieldInfo;
TFieldInfo = packed record
FieldName: ShortString;
FieldClass: TPersistentClass;
FieldOffset: integer
end;
TEnumFieldsProc = function(const FieldInfo: TFieldInfo; Data: Pointer): boolean; // False = stop
function GetFieldTable(AClass: TClass): PFieldTable;
procedure EnumFields(AClass: TClass; Data: Pointer; Proc: TEnumFieldsProc);
implementation
const
Delta = SizeOf(integer) + SizeOf(SmallInt) + 1;
function GetFieldTable(AClass: TClass): PFieldTable;
begin
if AClass <> nil then
Result := PFieldTable(Pointer(Integer(AClass) + vmtFieldTable)^)
else
Result := nil
end;
{$IFOPT R+}
{$DEFINE FldInfoRPlus}
{$R-}
{$ENDIF}
procedure EnumFields(AClass: TClass; Data: Pointer; Proc: TEnumFieldsProc);
var
FieldTable: PFieldTable;
FieldRec: PFieldRec;
Info: TFieldInfo;
i: integer;
begin
FieldTable := GetFieldTable(AClass);
if FieldTable <> nil then
with FieldTable^ do
begin
FieldRec := @Fields[0];
for i := 0 to Count - 1 do
with FieldRec^, Info do
begin
FieldName := Name;
FieldClass := ClassTable^.Classes[ClassIndex]^;
FieldOffset := Offset;
if Proc(Info, Data) then
FieldRec := PFieldRec(Integer(FieldRec) + Length(Name) + Delta)
else
Break
end
end
end;
{$IFDEF FldInfoRPlus}
{$R+}
{$UNDEF FldInfoRPlus}
{$ENDIF}
end.
Тестирующая программа (на форму кинуть TEdit, TButton, TListBox и назначить форме обработчик двойного клика по ней):
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
ListBox1: TListBox;
procedure FormDblClick(Sender: TObject);
private
procedure Check;
published
FMyField: TPersistent;
end;
function EnumFieldsProc(const FieldInfo: TFieldInfo; Data: Pointer): boolean;
begin
Result := True;
with FieldInfo, TStrings(Data) do
AddObject(Format('%s: %s ($%x)', [FieldName, FieldClass.ClassName, FieldOffset]), TObject(FieldOffset))
end;
procedure TForm1.Check;
begin
with ListBox1 do
begin
TEdit(Pointer(Integer(Self) + Integer(Items.Objects[0]))^).Text := 'OK';
TButton(Pointer(Integer(Self) + Integer(Items.Objects[1]))^).Caption := 'OK';
Self.Caption := TObject(Pointer(Integer(Self) + Integer(Items.Objects[2]))^).ClassType.ClassName
end
end;
procedure TForm1.FormDblClick(Sender: TObject);
begin
ListBox1.Clear;
EnumFields(ClassType, ListBox1.Items, EnumFieldsProc);
Check
end;
Юрий Зотов (2005-06-28 00:24:00) [8]Итак: все эти списки основаны на RTTI, а она генерится только для published и только для свойств и объектных полей.
Гы...
:о)
Ученик (2005-06-28 00:17:00) [9]>Юрий Зотов © (27.06.05 23:02) [44]
TFieldRec.XXX - индекс в FieldTable.FieldClassTable
DiamondShark (2005-06-28 00:09:00) [10]
> Ну, если быть точным, пропускает еще интерфейсного типа
> (помимо объектного).
Пропускает. Но в таблицу не записывает.
Просто Джо (2005-06-28 00:05:00) [11]
> Published поля не объектного типа даже компилятор не пропускает.
Ну, если быть точным, пропускает еще интерфейсного типа (помимо объектного).
DiamondShark (2005-06-27 23:47:00) [12]Мда...
Теперь понятно, почему там нет типа.
Published поля не объектного типа даже компилятор не пропускает.
Хитрые разработчики Дельфи сделали только минимум, необходимый для работы IDE.
Юрий Зотов (2005-06-27 23:02:00) [13]Посмотрел System.pas повнимательнее. Увы, тип TFieldInfo используется только в процедурах инициализации и финализации записей и массивов - поэтому похоже, что к сабжу он отношения не имеет. Поэтому и структура данных не совпадает.
Остается способ, код которого уже приводился. Вот бы еще узнать, что же хранится в таинственном поле TFieldRec.XXX
Юрий Зотов (2005-06-27 22:47:00) [14]> DiamondShark © (27.06.05 21:48) [40]
В D7 объявлено в implementation (причем TTypeInfo отличается от ее же декларации в TypInfo только словом packed):
type
PPTypeInfo = ^PTypeInfo;
PTypeInfo = ^TTypeInfo;
TTypeInfo = packed record
Kind: Byte;
Name: ShortString;
{TypeData: TTypeData}
end;
TFieldInfo = packed record
TypeInfo: PPTypeInfo;
Offset: Cardinal;
end;
PFieldTable = ^TFieldTable;
TFieldTable = packed record
X: Word;
Size: Cardinal;
Count: Cardinal;
Fields: array [0..0] of TFieldInfo;
end;
Но окно CPU показывает, что что-то тут не так (эта структура не соответствует расположенной адресу, хранящемуся в VMT по смещению vmtFieldTable). Надо копать.
default (2005-06-27 22:47:00) [15]вообщем бесполезно всё это
default (2005-06-27 22:44:00) [16]но это только для классовых и интерфейсных полей
в [0] требовались и другие типы
понятно что можно использовать приведение, но это всё неудобно...
DiamondShark (2005-06-27 21:48:00) [17]
> и покопать в сторону PFieldTable
А в Д5 такой нет. :(
Я из анализа FieldAddress вытаскивал.
Как она выглядит?
Юрий Зотов (2005-06-27 21:22:00) [18]> jack128 © (27.06.05 19:06) [35]
> DiamondShark © (27.06.05 20:31) [37]
Вот так-то оно лучше. Очень рад, что мой "наезд" привел-таки к тому, что нужно было сказать и сделать СРАЗУ.
Гы...
:о)
> Begin
Cтруктуру записи о поле (это TFieldRec в [37]) я не знал, поэтому пришлось сделать небольшой экскурс в память компьютера и поисследовать таблицу полей (благо, что когда-то делал то же самое с таблице методов). Вот что в итоге получилось:
procedure GetPublishedFields(Obj: TObject; List: TStrings);
procedure GetClassFields(AClass: TClass);
var
P: Pointer;
procedure IncPtr(Offset: integer);
begin
P := Pointer(Integer(P) + Offset)
end;
var
FieldCount: word;
FieldOffset: integer;
FieldName: ShortString;
i: integer;
begin
if AClass <> nil then
begin
P := AClass;
IncPtr(vmtFieldTable);
P := Pointer(P^);
if P <> nil then
begin
FieldCount := Word(P^);
IncPtr(6);
for i := 0 to FieldCount - 1 do
begin
FieldOffset := Integer(P^);
IncPtr(6);
FieldName := ShortString(P^);
IncPtr(Byte(P^) + 1);
List.AddObject(FieldName, TObject(FieldOffset))
end
end
end
end;
var
AClass: TClass;
begin
List.Clear;
if Obj <> nil then
begin
AClass := Obj.ClassType;
while AClass <> nil do
begin
GetClassFields(AClass);
AClass := AClass.ClassParent
end
end
end;
Это работает, дает имена полей и их смещения (по которым легко получить адреса полей). Но не дает о полях никакой другой информации (впрочем, как и код [37]), поэтому, если тип поля заранее неизвестен, то работать с ним все равно практически нельзя, несмотря на известный адрес.
Поэтому лучше воспользоваться советом [35] и покопать в сторону PFieldTable. Судя по декларациям в System.pas, оттуда можно вытащить PTypeInfo каждого поля, а это уже дает возможность нормально работать с ним.
DiamondShark (2005-06-27 20:33:00) [19]
> Count := Integer(tbl^);
Count := Word(tbl^);
DiamondShark (2005-06-27 20:31:00) [20]
> Юрий Зотов © (27.06.05 18:51) [31]
> > jack128 © (27.06.05 18:19) [23]
> > DiamondShark © (27.06.05 18:40) [27]
>
> Не считаю правильным заниматься формальными придирками к
> словам, выдергивая их из контекста ветки.
Утверждение "Все эти списки основаны на RTTI, а она генерится только для свойств (а не для полей)" является чушью вне зависимости от контекста.
> Если не согласны - прошу привести код, дающий список published-полей.
> Как вы понимаете, список published-свойств получается легким
> движением руки - а вот как быть с полями?
А так же. Если функцию для полей не включили в TypInfo.pas, это ещё не значит, что информации для полей нету.
Для версии 5:
procedure TForm1.Button2Click(Sender: TObject);
type
TFieldRec = packed record
Offset: Integer;
XXX: Word;
Name: ShortString;
end;
PFieldRec = ^TFieldRec;
var
tbl: Pointer;
cls: TClass;
Count: Word;
i: Integer;
begin
cls := Self.ClassType;
repeat
tbl := Pointer(Pointer(Integer(cls) + vmtFieldtable)^);
if tbl <> nil then
begin
Count := Integer(tbl^);
tbl := Pointer(Integer(tbl)+6);
for i := 0 to Count - 1 do
begin
Memo1.Lines.Add(PFieldRec(tbl).Name);
tbl := Pointer(Integer(tbl)+6+Length(PFieldRec(tbl).Name)+1);
end;
end;
cls := cls.ClassParent;
until cls = nil;
end;
> Сорри, но лучше бы по делу что сказали...
Ути-пути.