Легко собі уявити мову програмування.

На цьому етапі ми познайомимося зпоняттям інтерфейсу.

Інтерфейс- центральне поняттяCOM-таOLE-технології. Інтерфейс можна визначити як абстрактний клас, що не містить даних, для інформації про свій тип застосовує 16-байтовий ідентифікатор, при викликі методів якого використовується домовленістьsafecall.

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

Для інтерфейсів, як і й у класів, визначено поняття ієрархії. Суть цього поняття -кожен клас (або інтерфейс) може мати одного або кількох нащадків. У всіх нащадках цього класу (інтерфейсу) зберігаються всі методи (і дані для класів), які є у батьків. УDelphiкласи (так само як і інтерфейси) можуть мати лише одного з батьків, тому вони утворюють ієрархічне дерево. Його можна побачити вDelphiпід час виклику командиBrowserз менюView. У вершині ієрархічного дерева класів знаходиться родоначальник всіх класів -TObject, а інтерфейсів -IUnknown. Решта нащадків містить методиTObject (IUnknown).

Мал.1. Ієрархія інтерфейсів у браузері об'єктівDelphi

Всі методи інтерфейсів євіртуальнимиіабстрактними(такі класи вC++називаютьповністю абстрактними). Іншими словами, вони лише оголошені, але не реалізовані. Виникає цілком природне питання – а навіщо це треба? Длярозуміння цього необхідно згадати, що інтерфейс створюється в одному модулі (в основному наCOM-сервері), а використовується в іншому (COM-клієнт). Для того, щоб клієнт знав, які методи є в даному інтерфейсі і які параметри необхідно вказати під час виклику даного методу, застосовуються абстрактні методи. Отримавши посилання на створений у сервері інтерфейс, клієнт знає, наприклад, що єметод (функція)QueryInterfaceз першим параметром типуTGUIDі з другим типом нетипізованої змінної, який після виконання повертає змінну типуHResult. Відповідно, клієнт може викликати цей метод з цим списком параметрів і сервер повинен його виконати.

Слід звернути увагу, що головним для інтерфейсу є назва методу(QueryInterface), а список параметрів даного методу і те, яким по черзі він був оголошений при реалізації ієрархії інтерфейсів. МетодQueryInterfaceоголошується в інтерфейсіIUnknownтретім за рахунком (перші два методи цього інтерфейсу -AddRefтаRelease). Легко уявити мову програмування, якогоIUnknownоголошено, наприклад, так:

Слід звернути увагу до порядок приміщення змінних у тимчасову пам'ять - стек і те, що сервер очищає стек перед передачею управління процесом обчислень клієнту. У різних мовах програмування змінні можуть переміщатися в стек як ліворуч, слідуючи за списком формальних параметрів методу, так і праворуч наліво. Крім того, очищати стек може або метод, який був викликаний перед закінченням своєї роботи, або стек може очищатися після повернення в основний метод. Всі ці методи роботи зі стеком реалізовані в різних мовах програмування. Тому при виклику методівінших модулів необхідно домовлятися, як працювати зі стеком. Така домовленість(Calling Convention)міститься в директивіsafecall, що міститься після назви відповідного методу в описі інтерфейсу. Таким чином, на перший погляд абсолютно марний абстрактний метод інтерфейсу містить у собі дуже багато інформації: де його шукати у віртуальній таблиці, список змінних та їх типи, як ці змінні поміщаються у стек і хто його руйнуватиме після закінчення роботи. Директиваsafecallмістить ще й додаткову інформацію - у разі виняткової ситуації об'єкт виключення не потрапить до клієнта. Це важливо тому, що об'єкти виключення є мовно-залежними: вони по-різному реалізуються у різних мовах програмування. З іншого боку, різні модулі мають різні менеджери пам'яті. Відповідно, якщо об'єкт виключення потрапляє в інший модуль, він не може бути коректно зруйнований і ресурси не можуть бути віддані системі. Програми, написані наDelphi, намагаються зруйнувати такий об'єкт у будь-якому випадку, не аналізуючи, з якого модуля він з'явився, і в цьому випадку, як правило, генерується новий виняток.

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

Якби інтерфейси розрізнялися лише за іменами, то при випадковому збігу імен двох інтерфейсів (а це відбувалося б досить часто: ім'я зазвичай несе в собі смислове навантаження), реалізованих у різних модулях замість одного модуля завантажувався б інший. Тому для ідентифікації інтерфейсу використовується структура типу GUID (Global Universal Identifier), яка має розмір 16 байт (128 біт). Єдиний тип даних, визначених для інтерфейсу, - цеGUID. КоженCOM-інтерфейс містить свій унікальнийGUID. Якщо розробник реалізує новий інтерфейс, цей інтерфейс повинен матиGUID, причому унікальність повинна дотримуватися у межах даного комп'ютера розробника, а й усього світу загалом. Щоб отриматиGUIDдля новоствореного інтерфейсу в середовищіDelphi, достатньо натиснути клавішіCtrl+Shift+G. Питання – а що буде, якщо у двох різних місцях випадково буде згенеровано два однаковіGUID? Відповідь це питання полягає в динамічному діапазоніGUID. Він настільки величезний, що якщо поставити комп'ютер на безперервну випадкову генерацію GUID зі швидкістю 1000000 в секунду, то за весь час існування Всесвіту з ймовірністю 95% не будуть створені два однакових GUID .