Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Потрепаться";
Текущий архив: 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 по-умолчанию это отключено



Страницы: 1 вся ветка

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

Наверх















Память: 0.9 MB
Время: 0.044 c
1-46330
Arhangel
2002-04-03 19:53
2002.04.15
Как поменять системную дату?


14-46503
Keith
2002-03-02 22:31
2002.04.15
как тут все... того...


1-46307
Glonia Zbanov
2002-04-03 15:22
2002.04.15
WM_GETMINMAXINFO сразу для всех форм


1-46347
SeF
2002-03-27 02:55
2002.04.15
Запихивание в DLL


14-46519
DPro
2002-03-06 13:35
2002.04.15
Дорогие Мастера!





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский