Подорожуючи
по TObject. Або як воно працює  h2>
 Максим Ігнатьєв  p>
 Кожен клас у
Delphi є спадкоємцем TObject, і, відповідно,
володіє усіма його властивостями і методами. Це, поза сумнівом, корисний факт, але
які його методи і властивості, які його основні властивості і як їх можна
використовувати? Як ми побачимо трохи пізніше, дуже багато чого в реалізації TObject
направлено на опис об'єктної моделі Delphi.  p>
 Розглянемо його
опис детальніше.  p>
  p>
 TObject class =  p>
 constructor
Create;  p>
 procedure
Free;  p>
 class
function InitInstance (Instance: Pointer): TObject;  p>
 procedure
CleanupInstance;  p>
 function
ClassType: TClass;  p>
 class
function ClassName: ShortString;  p>
 class
function ClassNameIs (const Name: string): Boolean;  p>
 class
function ClassParent: TClass;  p>
 class
function ClassInfo: Pointer;  p>
 class
function InstanceSize: Longint;  p>
 class
function InheritsFrom (AClass: TClass): Boolean;  p>
 class
function MethodAddress (const Name: ShortString): Pointer;  p>
 class
function MethodName (Address: Pointer): ShortString;  p>
 function
FieldAddress (const Name: ShortString): Pointer;  p>
 function
GetInterface (const IID: TGUID; out Obj): Boolean;  p>
 class
function GetInterfaceEntry (const IID: TGUID): PInterfaceEntry;  p>
 class
function GetInterfaceTable: PInterfaceTable;  p>
 function
SafeCallException (ExceptObject: TObject;  p>
ExceptAddr: Pointer): HResult; virtual;  p>
  p>
 procedure
AfterConstruction; virtual;  p>
 procedure
BeforeDestruction; virtual;  p>
 procedure
Dispatch (var Message); virtual;  p>
  p>
 procedure
DefaultHandler (var Message); virtual;  p>
 class
function NewInstance: TObject; virtual;  p>
 procedure
FreeInstance; virtual;  p>
 destructor
Destroy; virtual;  p>
 end;  p>
 Відразу видно
методи класу, а їх функціональність, як відомо, не залежить від факту
існування примірника. Розглянемо детальніше кожний з методів.  P>
 Відразу хочу
обмовитися, методи - конструктори і деструктори насправді є
операторами, то є внутрішніми, що не залежать від їх реалізації в коді,
конструкціями.  p>
 Constructor
Create;  p>
 Всі об'єкти
створюються за допомогою виклику конструктора. Власне конструктор не зобов'язаний
називатися Create, просто це прийняте назва даного методу. Конструктор на
насправді є методом
класу, і в процесі його роботи викликаються такі методи:  p>
 NewInstance  p>
 InitInstance  p>
 Create  p>
 AfterConstruction  p>
 Насправді
виклик цих методів відбувається досить цікаво. У TObject конструктор не
виконує ніякої діяльності, однак, як кореневої клас ієрархії він створюється
на рівні RTM. Що ж відбувається? Після виклику конструктора RTM викликає метод
NewInstance, який виділяє область в пам'яті, узгоджуючи при цьому з
значенням vmtInstanceSize, яке формується при компіляції. У рамках виклику
NewInstance виконується виклик InitInstance, який заповнює поля методу
значеннями, позначеними в модифікаторах default, далі виконується код,
описаний в тілі процедури
Create (або тієї, що заявлена як конструктора), після чого управління
передається в точку, визначену в точці vmtAfterConstruction, яка за
замовчуванням вказує на метод AfterConstruction. Всі ці маніпуляції дозволяють
максимально спростити процес гнучкого створення екземпляра класу в рамках
об'єктної моделі Delphi. Таким чином, при створенні екземпляра класу
(об'єкта) ви можете «бути присутнім» на будь-якій його фазі. Сенс процедури
AfterConstruction полягає в тому, щоб виявити момент закінчення конструювання
класу. Зручність його використання полягає в тому, що він викликається тільки при
вдалому виконанні конструктора, що, самі розумієте досить вигідно. На
сьогоднішній момент тільки TCustomForm і TCustomDataModule перевантажують цей
метод спеціально для того, щоб виконати специфічні для них функції, так що
заважає нам зробити те ж саме? Але це вже питання конструювання класу.  P>
 Що ж
відбудеться при виникненні виняткової ситуації в рамках конструктора?
Тут важливо знати про те, що всі елементи класу вже створені і при
виникненні виняткової ситуації ми знаємо, що можна видалити. Так от при
виникненні виключення викликаються всі дії, пов'язані з руйнуванням --
виклик деструктора, все по повній програмі.  p>
 Важливо знати,
що при виклику конструктора
класу він викликається як конструктор і створює екземпляр, при
виклику ж конструктора в об'єкта
він викликається як процедура і нового екземпляра не створює, звідси отримують
свій початок помилки наступного роду:  p>
  p>
 Var  p>
 O: TObject;  p>
 Begin  p>
 O. Create;// Неправильний виклик  p>
 O: = TObject.Create;//
Коректний виклик  p>
 End;  p>
 При виклику
методу конструктора в об'єкта важливо відзначити той факт, що якщо в конструкторі
створюються поля-об'єкти, то тут виникає потенційна небезпека витоку пам'яті,
тому що нові екземпляри створюються, а старі не знищуються.  p>
 Procedure
Free;  p>
 Ця процедура ініціює
процес руйнування об'єкта в пам'яті. Чому ж не деструктор? Виклик деструктора
є коректним звільненням ресурсів для застарілого способу визначення
об'єктів object. Якщо ж подивитися на те, яким чином працює ця
процедура, то можна побачити дуже цікаву картину.  p>
  p>
 procedure TObject.Free;  p>
 asm  p>
 TEST EAX, EAX  p>
 JE @ @ exit  p>
 MOV ECX, [EAX]  p>
 MOV DL, 1  p>
 CALL dword ptr
[ECX]. VmtDestroy  p>
 @ @ exit:  p>
 end;  p>
 Що ж ми
бачимо? У першому рядку відбувається звірка покажчика на Self (себе) з нулем - а
не звільнили чи нас вже? Якщо ще ні, то відповідно вказівником на
vmtDestroy ми викликаємо реальний деструктор. В іншому випадку відбувається вихід
з процедури. Таким чином відбувається тривіальна "перевірка на дурня» з
боку RTM Delphi. Коли Ви робите ж деструктора ми безпосередньо звільняємо
(або не звільняємо, а даремно) ресурси об'єкта. Знову ж таки при звільненні ресурсів
виконується повний набір дій.  p>
 BeforeDestruction  p>
 FreeInstance
 p>
 Метод
FreeInstance викликає каскад процедур, спрямованих на звільнення всіх
захоплених ресурсів, у тому числі і динамічних масивів, Variant типів і
багато чого іншого. Це має бути корисно при виникненні виняткових
ситуаціях в конструкторі при вже створених внутрішніх динамічних структурах.
Це також дуже корисно як механізм збору сміття всередині об'єкта.  P>
 class function InitInstance (Instance: Pointer):
TObject;  p>
 Функція
ініціалізації екземпляра інформацією з VMT, при цьому враховується використання
інтерфейсів при спадкуванні. Важливо звернути увагу на те, що це функція
класу, фактично ця функція заповнює болванку об'єкта, створену функцією
NewInstance.  P>
 Procedure
CleanupInstance;  p>
 Процедура
повернення примірника до «невинному» змісту. При цьому використовуються
інформація, що зберігається в vmtInitTable і в vmtParent.  p>
 Function ClassType: TClass;  p>
 Повертає
клас об'єкта. А якщо бути більш точним, то повертається безпосередньо
покажчик на VMT.  p>
 class function ClassName: ShortString;  p>
 Повертає
назва класу. Використовується VMT.  P>
 class function ClassNameIs (const Name: string):
Boolean;  p>
 Виконує
звірку назви з назвою потрібного класу. Використовується при виконанні
оператора is.  p>
 class function ClassParent: TClass;  p>
 Віддає
вказівник на батьківський клас. Використовується при виконанні оператора is.  P>
 class function ClassInfo: Pointer;  p>
 Повертає
покажчик на RTTI інформацію про клас. Якщо клас скомпільований без
використання директиви $ M +, то повертається nil.  p>
 class function InstanceSize: Longint;  p>
 Розмір
екземпляра. Як видно з опису інформація про розмір і про RTTI зберігається в VMT
поза прив'язкою до конкретного екземпляру. Судячи з усього, ця інформація
формується під час компіляції.  p>
 class function InheritsFrom (AClass: TClass): Boolean;  p>
 Повертає
точна вказівка на те, що даний клас успадкований від шуканого. Ця функція
сканує VMT і батьків цього VMT на відповідність зазначеного класу.  p>
 class function MethodAddress (const Name: ShortString):
Pointer;  p>
 Сканує VMT
на наявність методу і при вдалому результаті повертає вказівник але нього. При не
знаходженні методу в "рідний" VMT сканується VMT батька і так до
тих пір, поки не буде знайдений (або не знайдено) адреса методу. Таким чином
здійснюється реалізація метаморфізму в об'єктної моделі Delphi.  p>
 class function MethodName (Address: Pointer): ShortString;  p>
 Функція обернено
попередньої.  p>
 Function FieldAddress (const Name: ShortString):
Pointer;  p>
 Доступ до полів.
Повертає вказівник на поле. Як завжди використовує VMT.  p>
 Function GetInterface (const IID: TGUID; out Obj):
Boolean;  p>
 Використовується
при спадкуванні інтерфейсів і повертає інтерфейс зазначених вище IID.  p>
 class function GetInterfaceEntry (const IID: TGUID):
PinterfaceEntry;  p>
 Повертає
точку входу інтерфейсу на вказаний IID.  p>
 class function GetInterfaceTable: PInterfaceTable;  p>
 Таблиця
інтерфейсів. Незважаючи на те, що заявлено використання нескінченного числа
інтерфейсів, у вихідному тексті ясно вказано на 10000 елементів таблиці
інтерфейсів. Я, зрозуміло, не хочу поставити експеримент і спробувати
перевищити цей ліміт, але прогрес йде такими темпами, що, боюся, через
деякий час цей ліміт буде вичерпано.  p>
 Function SafeCallException (ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;  p>
 Безпечна
обробка переривання, однак, використання цього методу безпосередньо в
TObject поверне Вам E_UNEXPECTED, то є щось несподіване. Викликається кожен
разу при виникненні виключення всередині коду об'єкта з вказівкою на об'єкт
винятки та адреса, що викликав виняток.  p>
 Procedure AfterConstruction; virtual;  p>
 Процедура,
викликається після створення екземпляра. Виклик процедури здійснюється за адресою,
прописаному в VMT. Прямий виклик ніде не прописаний, судячи з усього, ця
можливість прописана в RTM, де вказані всі виклики.  p>
 Procedure BeforeDestruction; virtual;  p>
 Процедура,
викликається до руйнування об'єкта.  p>
 Procedure Dispatch (var Message); virtual;  p>
 Внаслідок
використання Windows у якості базової платформи розробники вирішили не
проходити мимо основного способу обробки межоб'ектного взаємодії --
системи повідомлень. Цей спосіб якраз і реалізується цим методом. Вельми
розумно було помістити його саме в TObject, адже він є базовим для всіх
класів, визначених в рамках об'єктної моделі Delphi. Цей метод сканує
VMT на наявність обробника повідомлення, ID якого зазначений у перших 4 байтах
(довге слово, Cardinal) параметра Message і якщо не знаходить, то викликає
DefaultHandler. Тобто можна відловлювати події, що відбуваються не тільки у
елементів управління, а й у класів нижчої ієрархії.  p>
 Procedure DefaultHandler (var Message); virtual;  p>
 Оброблювач
подій за замовчуванням. Викликається методом Dispatch при не знаходженні
методу-обробника відповідного повідомлення.  p>
 class function NewInstance: TObject; virtual;  p>
 Створює
екземпляр класу. Розумно скористатися цією функцією для клонування об'єктів,
тому що, не знаючи початкового класу, можна створювати нові екземпляри вже готових
об'єктів без використання RTTI.  p>
 procedure FreeInstance; virtual;  p>
 Звільняє
ресурси примірника. Використання цього методу не вітається через його
тісному взаємозв'язку з VMT, тобто перевантаження цього методу повинна проводитися
з великою обережністю. Виклик ж методу безпосередньо в сукупності з InitInstance
може служити для того, щоб створити екземпляр «у собі», адже деякі завдання
вимагають відкату стану об'єкта на момент створення.  p>
 destructor Destroy; virtual;  p>
 Власне
деструктор. Викликається методом Free після посвідчення в тому, що примірник
поки існує. Є одне зауваження з приводу іменування деструктора - він
повинен називатися Destroy, це пов'язано з його віртуальністю, а відповідно і
перевантаженням. Якщо Ви назвете деструктор іншим ім'ям, то при спробі викликати
успадкований метод RTM не знайде опис методу з вашим ім'ям, а це
потягне за собою порушення функціональності процедури руйнування об'єкта.
Проте цікаво відзначити одну деталь. Наявність виклику успадкованого
деструктора не обов'язково, хоча і бажано - адже не всі розробники люблять
обробляти події часу виконання, а звільнення пам'яті, відведеної під
примірник, відбудеться без участі коду, описаного в деструктор.  p>
 Даючи опис
методам базового класу TObject, я намагався дати уявлення про об'єктної
моделі Delphi, про життєвому циклі об'єкта, про методи використання об'єктів в
власних програмах та правила перевантаження. Як видно з вищесказаного
основою для роботи з екземплярами є VMT, і використання RTTI не завжди
необхідно для виконання деяких специфічних операцій з екземпляром.
Використання ж RTTI, на мій погляд, не завжди виправдовується, однак при
написанні RunTime редакторів компонент це засіб досить зручно.  p>
 У результаті
вивчення вихідного коду виявився цікавий момент - при виклику будь-якого
методу в EAX знаходиться покажчик ... на VMT! Чи не це є явним вказівкою
на об'єктну орієнтованість Delphi?! Вивчаючи матеріали книги "Delphi in
nutshell "Рея Лішнера (Ray Lischner) я наткнувся на цікавий факт --
таблицю порівняння об'єктних моделей деяких мов, дозволю собі навести її
з деяким перекладом і доповненнями:  p>
 Підтримувані
можливості об'єктних моделей деяких мов програмування.  p>
 
  
  
   
    
    
 Можливість  p>
    
    
    
 Delphi  p>
    
    
    
    
    
    
 Java  p>
    
    
    
 Visual
    Basic  p>
    
   
   
    
    
 Спадкування  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Множинне спадкування  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
    
    
    
   
   
    
    
 Інтерфейси  p>
    
    
    
 +  p>
    
    
    
 Як чисто абстрактні класи  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
   
   
    
    
 Один базовий клас  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Метакласси  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Статичні поля класів  p>
    
    
    
 Як поля модуля  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Віртуальні методи  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Абстрактні методи  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Статичні методи класів  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Динамічні методи  p>
    
    
    
 +  p>
    
    
    
    
    
    
    
    
    
    
   
   
    
    
 Збір сміття  p>
    
    
    
 Інтерфейсні методи і динамічні
    деструктори  p>
    
    
    
    
    
    
 +  p>
    
    
    
 Інтерфейсні методи  p>
    
   
   
    
    
 Типи Variant  p>
    
    
    
 +  p>
    
    
    
    
    
    
    
    
    
 +  p>
    
   
   
    
    
 OLE automation  p>
    
    
    
 +  p>
    
    
    
    
    
    
    
    
    
 +  p>
    
   
   
    
    
 Статичний контроль типів  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Обробка винятків  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
   
   
    
    
 Перевантаження методів  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 поліморфні виклики  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Перевантаження операторів  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
    
    
    
   
   
    
    
 Методи - не члени класу  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
   
   
    
    
 Змінні - не члени об'єкта  p>
    
    
    
 +  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
   
   
    
    
 Властивості  p>
    
    
    
 +  p>
    
    
    
    
    
    
    
    
    
 +  p>
    
   
   
    
    
 RTTI  p>
    
    
    
 +  p>
    
    
    
 Тільки для операторів is і
    as  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Загальні типи (шаблони)  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
    
    
    
   
   
    
    
 Підтримка ниток  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Обробка повідомлень  p>
    
    
    
 +  p>
    
    
    
    
    
    
    
    
    
    
   
   
    
    
 Вбудований асемблер  p>
    
    
    
 +  p>
    
    
    
 У деяких реалізаціях  p>
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Inline функції  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
    
    
    
   
   
    
    
 Пакети  p>
    
    
    
 +  p>
    
    
    
    
    
    
 +  p>
    
    
    
    
   
   
    
    
 Друзі класу  p>
    
    
    
 Модульна видимість  p>
    
    
    
 +  p>
    
    
    
 Пакетна видимість  p>
    
    
    
    
   
  
  
  
 
 Тут видно
деякі особливості, які не є очевидними на перший погляд. Що ж таке
динамічні методи? Відразу варто зробити застереження - це модифікатор способу виклику
методу, і з цього його відразу треба поставити в один ряд з іншими способами
виклику методів - статичними, віртуальними і репредставітельнимі. Чим же вони
відрізняються і коли вони потрібні?  p>
 Статичні
методи (їхній аналог в Java - final,
фінальні) є не перевантажує методами, їх функціональність
остаточна, наприклад конструктор Create класу TObject - він порожній і ніякої додаткової діяльності не
несе, з цього виклик цього методу не поліморфа. З цього, якщо ви хочете
перевантажити статичний метод, то Вам доведеться заново описувати всю його
функціональність.  p>
 Віртуальні
методи це методи, які дозволяють формувати ланцюжки поліморфних викликів
за допомогою статичного зв'язування через таблицю віртуальних методів VMT. Це
виглядає приблизно так:  p>
 
  
  
   
    
    
 Метод  p>
    
    
 Посилання  p>
    
   
   
    
    
 DoOne  p>
    
    
    
 Self.DoOne  p>
    
   
   
    
    
 DoTwo  p>
    
    
    
 Self.Parent.DoTwo  p>
    
   
   
    
    
 DoThree
     p>
    
    
    
 Self.Parent.Parent.DoThree  p>
    
   
  
  
  
 
 Щоб бути
цілком точним, треба сказати, що в таблиці вказані всі віртуальні методи,
визначені в батьках плюс всередині самого класу. Якщо є перевантажені
методи, то в таблиці на відповідних місцях ставляться вхідні точки нових
методів, якщо ж ні - то вхідні точки методів батьків. Таким чином, можна
точно сказати, який метод треба викликати при зверненні до таблиці віртуальних
методів.  p>
 Динамічні
методи зберігають точки входу в спеціальній таблиці динамічних методів, ця
таблиця будується тільки для змінених або доданих методів. Таким чином
зберігатися менше інформації про клас, але виклик методів відбувається довше,
внаслідок лінійного пошуку методу в таблиці динамічних методів класу і його
батьків, на що витрачається деякий час.  p>
 Репредствавітельние
методи це методи, функціональність яких перевизначено повністю
щодо батьків. Взагалі-то вказівка директиви reintroduce не обов'язково, але це позбавить
Вас від зайвого попередження про перевантаження методу з боку компілятора.  P>
 Ще один
цікава особливість TObject
- Це зберігання на рівні VMT інформації про три методи з цікавою назвою: QueryInterface, AddRef і Release.
Тобто будь-який клас, створений в рамках об'єктної моделі COM Delphi є об'єктом!
Єдиним обмеженням тут є те, що для функціонування цих
методів необхідно успадкувати хоча б один інтерфейс, що і зроблено в рамках
іншого базового класу TInterfacedObject.
 p>
 Увага!
Забороняється передрук цієї статті або її частини без узгодження з автором.
Якщо ви хочете розмістити цю статтю на своєму сайті або видати в друкованому вигляді,
зв'яжіться з автором.  p>
 Список
літератури  h2>
 Для підготовки
даної роботи були використані матеріали з сайту http://coderpro.fatal.ru/
 p>