Путешествуя по TObject. Или как оно работает (8368-1)

Посмотреть архив целиком

Путешествуя по TObject. Или как оно работает

Максим Игнатьев

Каждый класс в Delphi является наследником TObject, и, соответственно, обладает всеми его свойствами и методами. Это, несомненно, полезный факт, но каковы его методы и свойства, каковы его основные свойства и как их можно использовать? Как мы увидим немного позже, очень многое в реализации TObject направлено на описание объектной модели Delphi.

Рассмотрим его описание поподробнее.


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;

Сразу видны методы класса, а их функциональность, как известно, не зависит от факта существования экземпляра. Рассмотрим поподробнее каждый из методов.

Сразу хочу оговориться, методы - конструкторы и деструкторы на самом деле являются операторами, то есть внутренними, не зависящими от их реализации в коде, конструкциями.

Constructor Create;

Все объекты создаются посредством вызова конструктора. Собственно конструктор не обязан называться Create, просто это принятое название данного метода. Конструктор на самом деле является методом класса, и в процессе его работы вызываются следующие методы:

NewInstance

InitInstance

Create

AfterConstruction

На самом деле вызов этих методов происходит достаточно интересно. В TObject конструктор не выполняет никакой деятельности, однако, как корневой класс иерархии он создается на уровне RTM. Что же происходит? После вызова конструктора RTM вызывает метод NewInstance, который выделяет область в памяти, согласуясь при этом со значением vmtInstanceSize, которое формируется при компиляции. В рамках вызова NewInstance выполняется вызов InitInstance, который заполняет поля метода значениями, обозначенными в модификаторах default, далее выполняется код, описанный в теле процедуры Create (или той, что заявлена в качестве конструктора), после чего управление передается в точку, определенную в точке vmtAfterConstruction, которая по умолчанию указывает на метод AfterConstruction. Все эти манипуляции позволяют максимально упростить процесс гибкого создания экземпляра класса в рамках объектной модели Delphi. Таким образом, при создании экземпляра класса (объекта) вы можете «поприсутствовать» на любой его фазе. Смысл процедуры AfterConstruction состоит в том, чтобы выявить момент окончания конструирования класса. Удобство его использования состоит в том, что он вызывается только при удачном выполнении конструктора, что, сами понимаете достаточно выгодно. На сегодняшний момент только TCustomForm и TCustomDataModule перегружают этот метод специально для того, чтобы выполнить специфичные для них функции, так что мешает нам сделать то же самое? Но это уже вопрос конструирования класса.

Что же произойдет при возникновении исключительной ситуации в рамках конструктора? Здесь важно знать о том, что все элементы класса уже созданы и при возникновении исключительной ситуации мы знаем, что можно удалить. Так вот при возникновении исключения вызываются все действия, связанные с разрушением - вызов деструктора, все по полной программе.

Важно знать, что при вызове конструктора класса он вызывается как конструктор и создает экземпляр, при вызове же конструктора у объекта он вызывается как процедура и нового экземпляра не создает, отсюда получают свое начало ошибки следующего рода:


Var

O : TObject;

Begin

O.Create; // Неверный вызов

O := TObject.Create; // Корректный вызов

End;


При вызове метода конструктора у объекта важно отметить тот факт, что если в конструкторе создаются поля-объекты, то тут возникает потенциальная опасность утечки памяти, так как новые экземпляры создаются, а старые не уничтожаются.

Procedure Free;

Эта процедура инициирует процесс разрушения объекта в памяти. Почему же не деструктор? Вызов деструктора является корректным освобождением ресурсов для устаревшего способа определения объектов object. Если же посмотреть на то, каким образом работает эта процедура, то можно увидеть интереснейшую картину.


procedure TObject.Free;

asm

TEST EAX,EAX

JE @@exit

MOV ECX,[EAX]

MOV DL,1

CALL dword ptr [ECX].vmtDestroy

@@exit:

end;


Что же мы видим? В первой строке происходит сверка указателя на Self (себя) с нулем - а не освободили ли нас уже? Если еще нет, то соответственно указателю на vmtDestroy мы вызываем реальный деструктор. В противном случае происходит выход из процедуры. Таким образом происходит тривиальная «проверка на дурака» со стороны RTM Delphi. При вызове же деструктора мы непосредственно освобождаем (или не освобождаем, а зря) ресурсы объекта. Опять же при освобождении ресурсов выполняется полный набор действий.

BeforeDestruction

FreeInstance

Метод FreeInstance вызывает каскад процедур, направленных на освобождение всех захваченных ресурсов, в том числе и динамических массивов, Variant типов и многого другого. Это должно быть полезно при возникновении исключительных ситуациях в конструкторе при уже созданных внутренних динамических структурах. Это также весьма полезно как механизм сбора мусора внутри объекта.

class function InitInstance(Instance: Pointer): TObject;

Функция инициализации экземпляра информацией из VMT, при этом учитывается использование интерфейсов при наследовании. Важно обратить внимание на то, что это функция класса, фактически эта функция заполняет болванку объекта, созданную функцией NewInstance.

Procedure CleanupInstance;

Процедура возврата экземпляра к «девственному» содержанию. При этом используются информация, хранящаяся в vmtInitTable и в vmtParent.

Function ClassType: TClass;

Возвращает класс объекта. А если быть более точным, то возвращается непосредственно указатель на VMT.

class function ClassName: ShortString;

Возвращает название класса. Используется VMT.

class function ClassNameIs(const Name: string): Boolean;

Выполняет сверку названия с названием необходимого класса. Используется при выполнении оператора is.

class function ClassParent: TClass;

Отдает указатель на родительский класс. Используется при выполнении оператора is.

class function ClassInfo: Pointer;

Возвращает указатель на RTTI информацию о классе. Если класс скомпилирован без использования директивы $M+, то возвращается nil.

class function InstanceSize: Longint;

Размер экземпляра. Как видно из описания информация о размере и о RTTI хранится в VMT вне привязки к конкретному экземпляру. Судя по всему, эта информация формируется во время компиляции.

class function InheritsFrom(AClass: TClass): Boolean;

Возвращает точное указание на то, что данный класс унаследован от искомого. Эта функция сканирует VMT и родителей этого VMT на соответствие указанному классу.

class function MethodAddress(const Name: ShortString): Pointer;

Сканирует VMT на наличие метода и при удачном результате возвращает указатель но него. При не нахождении метода в "родной" VMT сканируется VMT родителя и так до тех пор, пока не будет найден (или не найден) адрес метода. Таким образом осуществляется реализация метаморфизма в объектной модели Delphi.

class function MethodName(Address: Pointer): ShortString;

Функция обратна предыдущей.

Function FieldAddress(const Name: ShortString): Pointer;

Доступ к полям. Возвращает указатель на поле. Как всегда использует VMT.

Function GetInterface(const IID: TGUID; out Obj): Boolean;

Используется при наследовании интерфейсов и возвращает интерфейс указываемого IID.

class function GetInterfaceEntry(const IID: TGUID): PinterfaceEntry;

Возвращает точку входа интерфейса на указанный IID.

class function GetInterfaceTable: PInterfaceTable;

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

Function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; virtual;

Безопасная обработка прерывания, однако, использование этого метода непосредственно в TObject вернет Вам E_UNEXPECTED, то есть что-то неожиданное. Вызывается каждый раз при возникновении исключения внутри кода объекта с указанием на объект исключения и адрес, вызвавший исключение.


Случайные файлы

Файл
159685.rtf
5848-1.rtf
181514.rtf
154048.rtf
159386.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.