Подорожуючи по 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, тобто щось несподіване. Викликається кожного разу при виникненні виключення всередині коду об'єкта з вказівкою на об'єкт винятки та адреса, що викликав виняток.

Procedure AfterConstruction; virtual;

Процедура, викликається після створення екземпляра. Виклик процедури здійснюється за адресою, прописаному в VMT. Прямий виклик ніде не прописаний, судячи з усього, ця можливість прописана в RTM, де вказані всі виклики.

Procedure BeforeDestruction; virtual;

Процедура, яка викликається до руйнування об'єкта.

Procedure Dispatch (var Message); virtual;

Унаслідок використання Windows як базової платформи розробники вирішили не проходити повз основного способу обробки межоб'ектного взаємодії - системи повідомлень. Цей спосіб якраз і реалізується цим методом. Дуже розумно було помістити його саме в TObject, адже він є базовим для всіх класів, визначених у рамках об'єктної моделі Delphi. Цей метод сканує VMT на наявність обробника повідомлення, ID якого зазначений у перших 4 байтах (довге слово, Cardinal) параметра Message і якщо не знаходить, то викликає DefaultHandler. Тобто можна відловлювати події, що відбуваються не тільки у елементів управління, але і в класів нижчої ієрархії.

Procedure DefaultHandler (var Message); virtual;

Оброблювач подій за замовчуванням. Викликається методом Dispatch при не знаходженні методу-обробника відповідного повідомлення.

class function NewInstance: TObject; virtual;

Створює екземпляр класу. Розумно використовувати цю функцію для клонування об'єктів, так як, не знаючи вихідного класу, можна створювати нові екземпляри вже готових об'єктів без використання RTTI.

procedure FreeInstance; virtual;

Звільняє ресурси екземпляра. Використання цього методу не вітається з причини його тісному взаємозв'язку з VMT, тобто перевантаження цього методу повинна проводитися з великою обережністю. Виклик ж методу безпосередньо в сукупності з InitInstance може служити для того, щоб створити екземпляр «в собі», адже деякі завдання вимагають відкату стану об'єкта на момент створення.

destructor Destroy; virtual;

Власне деструктор. Викликається методом Free після посвідчення в тому, що примірник поки існує. Є одне зауваження з приводу іменування деструктора - він повинен називатися Destroy, це пов'язано з його віртуальністю, а відповідно і перевантаженням. Якщо Ви назвете деструктор іншим ім'ям, то при спробі викликати успадкований метод RTM не знайде опис методу з вашим ім'ям, а це потягне за собою порушення функціональності процедури руйнування об'єкта. Однак цікаво зазначити одну деталь. Наявність виклику успадкованого деструктора не обов'язково, хоча і бажано - адже не всі розробники люблять обробляти події, виконавчі, а звільнення пам'яті, відведеної під примірник, відбудеться без участі коду, описаного в деструкції.

Даючи опис методів базового класу TObject, я намагався дати уявлення про об'єктної моделі Delphi, про життєвий цикл об'єкта, про методи використання об'єктів у власних програмах та правила перевантаження. Як видно з вищесказаного основою для роботи з екземплярами є VMT, і використання RTTI не завжди необхідно для виконання деяких специфічних операцій з екземпляром. Використання ж RTTI, на мій погляд, не завжди здійснюється, проте при написанні RunTime редакторов компонент це засіб досить зручно.

У результаті вивчення вихідного коду виявився цікавий момент - при виклику будь-якого методу в EAX знаходиться покажчик ... на VMT! Чи не це є явною вказівкою на об'єктну орієнтованість Delphi?! Вивчаючи матеріали книги "Delphi in nutshell" Рея Лішнера (Ray Lischner) я наткнувся на цікавий факт - таблицю порівняння об'єктних моделей деяких мов, дозволю собі навести її з деяким перекладом і доповненнями:

Підтримувані можливості об'єктних моделей деяких мов програмування.

Можливість Delphi C + + Java Visual Basic
Успадкування + + +
Множинне успадкування +
Інтерфейси + Як чисто абстрактні класи + +
Один базовий клас + +
Метаклассов + +
Статичні поля класів Як поля модуля + +
Віртуальні методи + + +
Абстрактні методи + + +
Статичні методи класів + + +
Динамічні методи +
Збір сміття Інтерфейсні методи і динамічні деструктори + Інтерфейсні методи
Типи Variant + +
OLE automation + +
Статичний контроль типів + + +
Обробка винятків + + + +
Перевантаження методів + + +
Поліморфні виклики + +
Перевантаження операторів +
Методи - не члени класу + + +
Змінні - не члени об'єкта + + +
Властивості + +
RTTI + Тільки для операторів is і as +
Загальні типи (шаблони) +
Підтримка ниток + +
Обробка повідомлень +
Вбудований асемблер + У деяких реалізаціях +
Inline функції +
Пакети + +
Друзі класу Модульна видимість + Пакетна видимість

Тут видно деякі особливості, які не очевидні на перший погляд. Що ж таке динамічні методи? Відразу варто обмовитися - це модифікатор способу виклику методу, і з цього його відразу треба поставити в один ряд з іншими способами виклику методів - статичними, віртуальними і репредставітельнимі. Чим же вони відрізняються і коли вони потрібні?

Статичні методи (їхній аналог в Java - final, фінальні) є не перевантажуються методами, їх функціональність остаточна, наприклад конструктор Create класу TObject - він порожній і ніякої додаткової діяльності не несе, з цього виклик цього методу не поліморфи. З цього, якщо ви хочете перевантажити статичний метод, то Вам доведеться заново описувати всю його функціональність.

Віртуальні методи це методи, які дозволяють формувати ланцюжки поліморфних викликів за допомогою статичного зв'язування через таблицю віртуальних методів VMT. Це вигладить приблизно так:

Метод Посилання
DoOne Self.DoOne
DoTwo Self.Parent.DoTwo
DoThree Self.Parent.Parent.DoThree

Щоб бути повністю точним, треба сказати, що в таблиці вказані всі віртуальні методи, визначені в батьках плюс всередині самого класу. Якщо є перевантажені методи, то в таблиці на відповідних місцях ставляться вхідні точки нових методів, якщо ж ні - то вхідні точки методів батьків. Таким чином, можна точно сказати, який метод треба викликати при зверненні до таблиці віртуальних методів.

Динамічні методи зберігають точки входу в спеціальній таблиці динамічних методів, ця таблиця будується тільки для змінених або доданих методів. Таким чином зберігатися менше інформації про клас, але виклик методів відбувається довше, внаслідок лінійного пошуку методу в таблиці динамічних методів класу та його батьків, на що витрачається деякий час.

Репредствавітельние методи це методи, функціональність яких перевизначена повністю щодо батька. Взагалі-то вказівка ​​директиви reintroduce не обов'язково, але це позбавить Вас від зайвого попередження про перевантаження методу з боку компілятора.

Ще одна цікава особливість TObject - це зберігання на рівні VMT інформації про три методи з цікавою назвою: QueryInterface, AddRef і Release. Тобто будь-який клас, створений у рамках об'єктної моделі Delphi є COM об'єктом! Єдиним обмеженням тут є те, що для функціонування цих методів необхідно успадкувати хоча б один інтерфейс, що і зроблено в рамках іншого базового класу TInterfacedObject.

Увага! Передрук цієї статті або її частини без узгодження з автором. Якщо ви хочете мати цю статтю на своєму сайті або видати в друкованому вигляді, зв'яжіться з автором.

Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Реферат
31.5кб. | скачати


Схожі роботи:
Як працює ринок
Рейдерство як воно є
Вимоги обслуговуючому персоналу що працює з електроустаткуванням 2
Вимоги обслуговуючому персоналу що працює з електроустаткуванням
Возз`єднання Росії та Україні можливе воно
Спецпереселення до Сибіру 1930-го року чи було воно вигідним
Зіткнення цивілізацій і що воно може означати для Росії
Проектування виробничих ділянок підприємства працює за індивідуальними замовленнями населення
Проект виробничих ділянок швейного підприємства працює за індивідуальним замовленням
© Усі права захищені
написати до нас