Форум: "Потрепаться";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.04.15;
Скачать: [xml.tar.bz2];




Вниз

Головоломка на знание принципа работы компилятора Object Pascal 


Алексей Петров   (2002-03-05 16:35) [0]

Суть следующая: как создать объект-потомок от TComponent зная его имя и не используя регистрации объектов.
Задачка всплыла в процессе обсуждения http://delphi.mastak.ru/cgi-bin/forum.pl?look=1&id=1015316367&n=3

Сразу предупреждаю, что готового кода, решающего задачу я не писал и в качестве решения не прошу, т.к. его будет довольно много.
Нужно только описание идеи.



Алексей Петров   (2002-03-05 16:49) [1]

> MBo © (05.03.02 16:36)
> а ограничения?
> Если мы в runtime попросим создать компонент, скажем, stringgrid, о котором Exe никакого понятия не имеет - и финиш...

Если в Exe нет такого компонента, то естественно создать его будет нельзя.



vuk   (2002-03-05 17:15) [2]

Кстати, если в лоб адреса перебирать (начиная с адреса TObject) множно много чего найти...



VuDZ   (2002-03-05 18:18) [3]

хватит на людьми издеваться, расказывай свою мыслю



Алексей Петров   (2002-03-05 18:33) [4]

Да я на нее несколько раз намекал.
и vuk вполне в тему пишет...



Алексей Петров   (2002-03-05 18:34) [5]

Подробно утром напишу, когда времени побольше будет...



vuk   (2002-03-05 18:41) [6]

Я могу сказать что то, что я предлагаю, это решение довольно-таки тупое и сделанное "в лоб", методом грубой силы. Однако список всех классов получить удалось. Есть, похоже, правда одно место в котором алгоритм дает небольшой сбой. Да и работает все это дело долго, а вникать в подробности просто лень. :o)



Shaman_Naydak   (2002-03-05 22:33) [7]

Мда, Алексей, задал ты задачку..
Основная проблема в том, что классы хранятся в виде дерева, где потомок указывает на предка, а наоборот не пройтись ;(
Посмотрел я как подымает TReader.
Ну что я могу сказать..
Тут комбинируются 2 способа.
1. Общеизвестный поискать FindClass"ом (найдет тех, кого через RegisterClass загнали в ClassList.
2. От заданного класса конечного класса он проходится по всем полям объектам, если не находит подымается вверх.

=> из 2 могу предложить след. уродский способ:
пройтись по всем компонентам TApplication"a, передав класс каждого из них в искалку.
Искалка проходится по всем полям объектам и для КАЖДОГО! вызывает рекурсивно себя же..
после списка полей переходит на проверку ParentClass и.т.д.
Результат складируется в TList"e (Жаль, что до борландовского ClassList"a не достучаться, он в implementation"e).

Так можно найти не только TComponent"ы..
Правда ясен пень создать здоровый объект можно только у наследников TComponent"ы, так как у него конструктор виртуальный.

Жутко заинтересовался, буду ждать стоящих идей решения.
Могу в принципе запрограммить то, что наговорил..
правда мне этот алгоритм не нравится, однако!



MBo   (2002-03-06 07:26) [8]

>Shaman_Naydak
Я тоже вчера копался с reader, пытался подправить FindComponentClass, не вышел пока каменный цветок.
>заданного класса конечного класса
в этом-то и проблема, задать класс по имени. Насколько я понимаю, только прямой вызов RegisterClass ДЛЯ КАЖДОГО КЛАССА или создание его аналога создает удобоваримый доступ к классам через ClassList. А с нахождением класса по имени через
vmtClassName=-44; у меня пока тоже проблемы- не хватает знаний о системе хранения объектов/классов.
>ClassList"a не достучаться, он в implementation"e
а толку-поправив classes, можно, а он пустой (или не все классы)



MBo   (2002-03-06 08:39) [9]

вот подход, позволяющий создавать компоненты, имеющиюхся на форме или ее подэлементах (при введении рекурсивного обхода) типов
Не радикально, но чем богаты...

function TForm1.FindSuch(Root:TComponent;const s: String): TComponent;
var i:integer;
begin
Result:=nil;
for i:=0 to root.componentcount-1 do begin
if sametext(root.Components[i].classname,s) then begin
Result:=Root.Components[i];
Break;
end;
end;
end;

procedure TForm1.Button6Click(Sender: TObject);
type
TWCClass = class of TWinControl;
var
Obj:TWinControl;
ObjClass:TWCClass;
s:string;
c:tcomponent;
begin
s:=edit1.text;
c:=FindSuch(TComponent(self),s);
if c<> nil then begin
ObjClass:=TWCClass(c.classtype);
if ObjClass<>nil then begin
Obj:=ObjClass.Create(self);
Obj.Parent:=self;
Obj.SetBounds(10,10,100,20);
end;
end;
end;



Алексей Петров   (2002-03-06 08:41) [10]

> Shaman_Naydak
Таким образом вы многое повытаскиваете. А еще если к вашей искалке добавить проход по ParentClass до TObject вы сможете очень хороший кусок иерархии объектов выкусить.

Расскажу свою мысль. В общем vuk решение уже называл по сути и я коротко на нее как-то намекал в форуме :)

Но как обещал, идея хаккерская и к сожалению код, её реализующий, может потребовать модификации при переходе с версии на версию.

Идея:
Для того, чтоб создать экземпляр класса нужно найти его VMT. Далее адрес VMT класса заносим в переменную типа class of ... и спомощью этой переменной можно вызвать виртуальный констуктор (потому только TComponent, как и подметил Shaman_Naydak). Основная сложность - вытащить список VMT-ов.

Несколько наблюдений по структуре exe-файла "made by Delphi":
1. В памяти (конкретно в сегменте кода) описания объектов лежат последовательно без пустот и вкрапления прочей информации (не считая выравнивания на двойное слово).
2. TObject хранится всегда самым первым. И ссылку на его VMT у нас уже есть: Pointer(TObject)

Собственно оснавная сложность: понять, сколько байт нужно пропустить, чтоб попасть на следующий объект.

Перый подход: Минимальный размер VMT - -vmtSelfPtr = 76 (для D5) - это размер служебной информации для класса.
Прибавляем к предыдущеум указателю -vmtSelfPtr и далее проверяем не на объект ли попали. Если нет - добавляем 4 и снова проверяем, пока не найдем или пока не уйдем за пределы сегмента кода. (Предварительно стоит выяснить, где кончается нужная память с помощью VirtualQuery).
Проверка того, что поинтер может быть TClass-ом довольно проста:
1) Pointer((Ptr+vmtTypeInfo)^) обязан показывать на кусок самого exe-шника, что выясняется с помощью VirtualQuery
2) PShortString((Ptr+vmtClassName)^) должен быть и быть корректным идентификатором по синтаксису Delphi.
3) (Ptr+vmtInstanceSize)^ должен быть разумным: как минимум положительным (>=4). Можно еще ограничение наложить <1GB а можно и побольше немного
4) Pointer((Ptr+vmtParent)^) должен указывать на ранее выкопанный объект (можно проверять просто на объект по выше приведенным критериям), кроме объекта TObject у которого тут nil.



Алексей Петров   (2002-03-06 08:57) [11]

Есть некоторая вероятность ложных срабатываний при первом подходе к сканированию, но она очень мала. А вот скорость сканирования - получится так себе.

Второй подход: пошустрее в Run-time, но с существенно большим объемом кодирования: Нужно для объекта посчитать размер его VMT и всех потрохов.
в отрицательной части VMT прописаны указатели на дополнительные данные об объекте. Все они находятся между нулевм смещением текущего VMT и началом следующей таблицы. Описание структуры всех этих таблиц есть в моулях System.pas и TypInfo.pas. Единственная таблица, для которой я не нашел явного описания - таблица динамических методов, но для нее структура легко восстанавливается при просмотре функции GetDynaMethod из модуля System.
Размер для них всех считается. Таким образом получается конец описания текущего объекта, а прямо следом за ним должен быть SelfPtr следующего объекта.



Shaman_Naydak   (2002-03-06 12:30) [12]

>> Алексей Петров
Да, так можно их повытаскивать, но геморрно.
Я так и думал, что они лежат подряд.. но вечером ковыряться было неохота..
А если после классов они в сегмент еще что-нидь положат, это ж будет совсем не гуд, однако.
Кстати, а не проверял ли как оно выглядит в памяти при компиляции с пакетами, там ситуация значительно осложняется :(

И что это ребята из Borland примитивную цепочку не сбацали, где один класс ссылается на следующий. Всем бы было хорошо, да и им не пришлось бы костыли делать в виде RegisterClass, GetClass.



vuk   (2002-03-06 12:49) [13]

to Алексей Петров:
Есть еще одна тонкость - когда остановить перебор адресов в памяти. Я решил ее так: в главном модуле проекта объявил класс и прописал создание его экземпляра с последующим удалением (чтобы компилятор его не выкинул). Суть в том, что этот класс всегда будет располагаться в памяти последним и имя его я знаю. Как только обнаруживаю этот класс, прекращаю перебор.

to Shaman_Naydak:
>А если после классов они в сегмент еще что-нидь положат, это ж
>будет совсем не гуд, однако.
Ну оно, вроде бы, так и есть, поскольку между адресами последнего класса в одном модуле и первого класса в другом обычно получается некоторый разрыв. Страшного в этом ничего нет. Ну попали не туда - в крайнем случае AV вылезет, его и проигнорировать можно. :o)

>Всем бы было хорошо, да и им не пришлось бы костыли делать в
>виде RegisterClass, GetClass.
RegisterClass не всю VCL всего раза 3 встречается. Да и практическая применимость (да и необходимость) этого метода с поиском всех классов довольно-таки сомнительна...



Алексей Петров   (2002-03-06 15:23) [14]

С пакетами все на порядок проще будет.
Для каждого класса в пакете есть своя точка входа через которые все вытаскивается.

А вот для exe, который использует пакеты - нужно будет взять любой "точно свой" класс и пройтись по нему не только вперед, но и назад таким-же образом (здесь правда метод №2 не пройдет.)



VuDZ   (2002-03-06 16:54) [15]

Если я правильно понял, то в exe Делфи сохраняет имя класса?



vuk   (2002-03-06 17:22) [16]

to VuDZ:
Да. Это является частью модели RTTI.



VuDZ   (2002-03-06 17:35) [17]

всё ясно, я забыл - у меня в VC по-умолчанию это отключено




Форум: "Потрепаться";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.04.15;
Скачать: [xml.tar.bz2];




Наверх






Память: 0.8 MB
Время: 0.038 c
7-46527           Fellomena             2002-01-18 14:06  2002.04.15  
API, System resources and ******* (inside)


1-46397           @andrew               2002-04-01 17:54  2002.04.15  
Привет Всем! Вопрос по DLL-ке.


1-46433           DenKop                2002-04-01 13:07  2002.04.15  
Шифрование с помощью RSA RC5.


1-46418           Nestor                2002-04-02 18:04  2002.04.15  
InstallShield


7-46520           amamed_3071           2002-01-18 13:28  2002.04.15  
где находится файл?