В нашей небольшой серии по обратной разработке (reverse engineering) недокументированных полей VMT классов Delphi, мы видели поле
FieldTable
. Это поле указывает на структуры, которые описывают published поля класса. В Delphi published поля должны быть ссылками на объекты и в основном используются формами (form) и модулями данных (datamodule) для хранения ссылок на свои компоненты в логически именованных полях для удобства доступа (потому что альтернативой к полям является использование массива Components
, что включает в себя поиск и преобразования типов).Delphi RTL содержит всего один общедоступный метод, который получает доступ к таблице полей:
TObject.FieldAddress
. Этот метод возвращает адрес published поля по заданному имени:
function TObject.FieldAddress(const Name: ShortString): Pointer; asm // ... MOV ESI,[EAX].vmtFieldTable // ... end;Этот метод использует компонентной подсистемой в реализации private метода
TComponent.SetReference
, чтобы найти поле у владельца компонента, которое соответствует имени компонента. Если компонент находит правильно названное поле у своего владельца, то он присваивает себя в это поле (или записывает nil
, если компонент удаляется):
procedure TComponent.SetReference(Enable: Boolean); var Field: ^TComponent; begin if FOwner <> nil then begin Field := FOwner.FieldAddress(FName); if Field <> nil then if Enable then Field^ := Self else Field^ := nil; end; end; procedure TComponent.InsertComponent(AComponent: TComponent); begin // … AComponent.SetReference(True); // … end; procedure TComponent.RemoveComponent(AComponent: TComponent); begin // … AComponent.SetReference(False); // … end; procedure TComponent.SetName(const NewName: TComponentName); begin // … SetReference(False); ChangeName(NewName); SetReference(True); // … end;Вот как компонентные поля вашей формы автоматически получают свои значения. И если вы освобождаете компонент в run-time, соответствующее поле автоматически устанавливается в
nil
. Здорово, да?! :-)Мы скоро посмотрим на структуру таблицы полей, а сейчас просто скажем, что таблица полей должна содержать имя каждого поля и смещение поля в экземпляре объекта, чтобы
FieldAddress
могла работать. Заметьте, что таблица полей является частью VMT и, значит, частью класса, а не отдельного экземпляра объекта. Вот почему таблица полей не может содержать прямой адрес поля, а только смещение - потому что адрес будет своим у каждого конкретного экземпляра класса (объекта). Смещение нужно скомбинировать с адресом объекта (добавить), чтобы получить реальный адрес поля в run-time.Есть и другой, закрытый, метод, который также обращается к таблице полей. Модуль
Classes
содержит подпрограмму BASM в своей секции implementation, называемую GetFieldClassTable
. Эта подпрограмма получает доступ к другой части таблицы полей - той, что содержит ссылки на классы:
function GetFieldClassTable(AClass: TClass): PFieldClassTable; asm MOV EAX,[EAX].vmtFieldTable // … end;Эта подпрограмма является частью самой низкоуровневой внутренней части реализации сериализации компонентов в
TReader
. Вложенные вызовы, которые приводят к вызову GetFieldClassTable
, начинаются с вызова общедоступного метода TReader.ReadComponent
. А всё дерево вызовов выглядит примерно так:
TReader.ReadComponent CreateComponent (вложенная подпрограмма) FindComponentClass GetFieldClass GetFieldClassTable FindExistingComponent (вложенная подпрограмма) FindComponentClass GetFieldClass GetFieldClassTableЛогика
FindExistingComponent
обрабатывает формы с визуальным наследованием, модуля данных (datamodule) и фреймы (frame). CreateComponent
создаёт новый компонент по описанию из DFM потока по строке имени класса компонента. FindComponentClass
работает как локальное проецирование строк имён классов на ссылки на классы TClass
. Вместо использования безотказной глобальной подпрограммы GetClass
, которая используется Delphi в design-time, FindComponentClass
сначала ограничивает свой поиск до уникального списка типов классов объявленных published полей. Видите ли, в дополнение к ассоциации имя-смещение таблица полей также содержит список всех типов классов, используемых published полями в классе.Когда вы проектируете форму, то у вас есть малоизвестная возможность просто удалить поле компонента из формы, если вы никогда не обращаетесь к нему из кода. Кроме того, вы можете просто очистить свойство
Name
компонента. Это превратит компонент в безымянный и IDE сама удалит объявление поля. Всё это позволяет немного уменьшить размер DFM и немного улучшить производительность загрузки в run-time.Но вам нужно быть осторожным с этим трюков. Если вы удалите все поля, имеющие тип определённого класса, загрузка компонента из DFM в run-time будет неудачной, вызывая ошибку вроде такой:
--------------------------- Debugger Exception Notification --------------------------- Project richedit.exe raised exception class EClassNotFound with message 'Class TLabel not found'. Process stopped. Use Step or Run to continue. --------------------------- OK Help ---------------------------Или (вне отладчика):
--------------------------- Rich Edit Control Demo --------------------------- Class TLabel not found. --------------------------- OK ---------------------------Вы уже должны понимать, почему вы видите эту ошибку. Класс
TReader
использует список published полей, чтобы получить список классов компонентов по имени класса, чтобы можно было сконвертировать имя класса из DFM в ссылку на реальный класс TComponent
. Если ссылка на какой-то класс не представлена в таблице полей класса, TReader
не сможет создать компонент, так что он просто возбуждает исключение EClassNotFound
, которое вы и видите выше. Заметьте, что TReader
также откатывается к использованию (более медленной) функции GetClass
, прежде чем окончательно возбудить исключение. Это означает, что если вы удаляете все published поля определённого класса, вы должны вызвать RegisterClass
на этот класс в initialization секции своего проекта:
//… initialization RegisterClass(TLabel); end.И тогда вы можете безболезненно удалить все поля класса
TLabel
из класса формы.Ok, этот рассказ должен дать вам основное представление о том, почему в Delphi есть published поля, что за RTTI информация хранится вместе с ними и как VCL её использует для выполнения магии сериализации DFM в design-time и run-time. Ассемблерный код
TObject.FieldAddress
и GetFieldClassTable
вместе с полезными объявлениями типов модуля Classes
дают нам несколько полезных подсказок к раскладке таблицы полей в памяти (структуре RTTI таблицы полей).В следующем посте мы познакомимся поближе со структурой таблицы полей и напишем Pascal-код и вспомогательные методы для работы с ней. Следите за обновлениями!
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.