конспект лекцій (ТСПП). Конспект лекцій з дисципліни 07 технологія створення програмних продуктів напряму 050101 Компютерні науки
Скачать 14.87 Mb.
|
6.1. Вступ до компонентного програмування.У цьому розділі буде дане тільки дуже коротке введення в проблематику компонентного програмування. Компонентне програмування - спроба вирішити ті проблеми, які виникають при використанні ТОП. Основна ідея -поширення класів в бінарному виді (тобто не у вигляді початкового коду) і надання доступу до методів класу через строго певні інтерфейси, що дозволяє зняти проблему несумісності компіляторів і забезпечує зміну версій класів без перекомпіляції застосувань, що використовують їх. Тут варто відмітити, що роль інтерфейсів в COM значно важливіша, ніж роль посередника. Вусі в COM починається з інтерфейсів. Сморід визначаються першими і задають семантику деякого сервісу. Різні класи можуть реалізовувати завдань інтерфейс, забезпечуючи тим самим поліморфізм на новому рівні. Є різні технології, що реалізовують парадигму компонентного програмування. Серед них COM (DCOM, COM+) CORBA, .Net. Для визначеності зупинимося на підході, використовуваному в COM. Компонент - це сховище (у вигляді DLL або EXE файлу) для одного або декількох класів. Вусі, що знає клієнт про клас, це його унікальний ідентифікатор і один або декілька інтерфейсів, що забезпечують доступ до реалізованим цим класом методам. Допускається реалізація компонента і клієнта, що використовує його, на різних мовах (Visual C++, Visual Basic). У реєстрі системи зберігається інформація про місце розташування компонента, що утримує цей клас (на локальному або видаленому комп' ютері). Це дозволяє системі прозоро для клієнта перенаправляти виклики методів до потрібного компонента і повертати результати. Таким чином, забезпечується виконання двох важливих принципів компонентного програмування :
Основні особливості компонентного програмування можна коротко охарактеризувати таким чином: Інкапсуляція У COM інкапсуляція знаходиться на більш високому рівні чим в ТОП. Між клієнтом і реалізацією класу знаходяться інтерфейси. Інтерфейс - абстрактний базовий клас, який не має елементів даних і який являється випрямляємо нащадком не більше ніж одного іншого інтерфейсу. Реалізація методів цього інтерфейсу виконується в класі який є нащадком даного і, можливо, ще інших інтерфейсів. При дотриманні цих обмежень різні компілятори генерують еквівалентний код для викликів методів інтерфейсу з боку клієнта. Клієнт без перекомпіляції може викликати методи нової версії класу (при збереженні інтерфейсу). Нова версія може мати розширену функціональність за рахунок додавання нових інтерфейсів. Ці нові інтерфейси можуть використовуватися новими клієнтами, старі ж клієнти продовжують працювати із старими інтерфейсами, не знаючи про існування нових. Спадкоємство інтерфейсів На відміну від ТОП, технологія компонентного програмування не підтримує спадкоємство реалізації класу. Наслідують тільки інтерфейси. Кожен інтерфейс отримує свій унікальний ідентифікатор і може реалізовуватися в різних класах, що включаються в різні компоненти. Новий інтерфейс може наслідувати раніше описаним інтерфейсам. Наприклад, в COM, будь-який інтерфейс повинний наслідувати стандартному інтерфейсу IUnknown. Спадкоємство інтерфейсу зокрема означає, що при реалізації методів нового інтерфейсу будуть реалізовані і усі методи описані в успадковному інтерфейсі. А як же бути з ідеєю повторного використання коду? У рамках технології компонентного програмування розробник нового компонента не використовує початковий код раніше реалізованого компонента. Але він (розробник) може додати функціональність старого компонента до функціональності нового. Це робиться за допомогою одного з двох методів. Контейнеризація Новий компонент включає (за допомогою спадкоємства) у свій інтерфейс інтерфейс старого компонента і реалізує його методи просто викликаючи методи старого компонента (делегування). При цьому старий компонент не знає, що він працює в інтересах іншого компонента. Але і новий компонент не отримує нову функціональність абсолютно задарма. Він вимушений бути посередником між клієнтом і старимо компонентом, що, природно, позначається напродуктивності. Агрегація При використанні цього підходу старий компонент знає про ті, що його використовує інший компонент. Більше того він спеціально проектується для забезпечення такої можливості. Зате і новий (що агрегує) компонент не працює посередником - виклики клієнта, що відносяться до старого компонента, прямують прямо йому. Але клієнт цього не помічає, йому здається, що він працює з одним новим компонентом. Поліморфізм Якщо описів деякий інтерфейс, то будь-яке число класів можуть реалізовувати його будь-яким способом, на будь- якій мові ((підтримувальному COM). Звичайно, при цьому не повинна мінятися семантика інтерфейсу. Іншими словами, клієнтові не важливо, хто і як реалізував інтерфейс, що цікавить його. У усіх випадках він отримає саме ті, що чекає. Бінарне представлення Як вже відзначалося раніше, компоненти поширюються і використовуються в бінарному виді, тобто у вигляді ''чорного ящика''. Це дозволяє вирішити багато проблем, згаданих раніше при описі недоліків ТОП, і, крім того, дає нові можливості. Наприклад, використання різних мов програмування при реалізації компонентів і клієнтів, що використовують їх. Інфраструктура для розподілених застосувань Багато що з цієї області включається в саму архітектуру системи, що реалізовує технологію компонентного програмування, багато що забезпечується у вигляді додаткових сервісів. Наприклад, у разі COM, при розміщенні компонента і клієнта в різних процесах (на одному або на різних комп'ютерах), автоматичний формується канал передачі даних, який забезпечує виклик методів, передачу параметрів і повернення результатів. Як приклад додаткових сервісів, реалізованих в COM+, можна згадати забезпечення таких важливих для розподіленого застосування сервісів як безпеку, транзакції балансування завантаження серверів, асинхронний доступ до компонент, підтримка публікації і підписки на події і тому подібне. Архітектура COM+ Почнемо із структури додатки. Поняття додаток, клас, інтерфейс, метод утворюють наступну ієрархію : додаток / клас / інтерфейс / метод. Іншими словами, додаток містить один або декілька класів (компонентів), кожен клас реалізує один або декілька інтерфейсів, і кожен інтерфейс описує один або декілька методів. Причому кожен клас може входити не більше ніж в одне застосування. Є два типи додатків: бібліотечні і серверні. Бібліотечне оформляється у вигляді DLL і завантажується в адресне простір клієнта. Серверне також оформляється у вигляді DLL, але при його активації запускається сурогатний процес ((dllhost.exe), в адресний простір якого і завантажується ця DLL. Тип додатка визначається при його створенні. Наприклад, при використанні Component Services задається приписуваний усьому застосуванню атрибут, задаючий його тип активації - бібліотечне або серверне застосування. Класи бувають конфігуровані і неконфігуровані. Конфігурований клас включень в деяке застосування і отже, йому приписані атрибути. Клас, не включень в додаток, не має атрибутів і зареєстрований тільки в реєстрі системи. Тепер розглянемо, як виглядає додаток під година виконання. Вусі, що говорилося раніше про процеси, потоки апартаментах має місце і в COM+. Але ця архітектура ускладнюється введенням нових елементів: контекст і активність. Почнемо з контексту. Кожен об' єкт інкапсулює дані і методи. Зазвичай говорять, що дані описують стан об' єкту. Проте в COM+ дані, інкапсульовані в об' єкті, не визначають повністю стан об' єкту. Ці дані визначають тільки ту частину стану об' єкту, яка пов'язана з бізнес - логікою додатка. Але конфігурований об' єкт в COM+ бере доля також в транзакціях і в інших складних процесах, підтримуваних сервісами COM+. Частина стану об' єкту, що відбиває його вимоги до середовища виконання і ті, як він використовує сервіси в даний момент годині, називається контекстом об' єкту. Для збереження контексту об' єкту використовується так звань об' єкт контексту, який автоматичний формується при активації об' єкту і супроводжує об' єкт до його деактивації. Дістати доступ до об' єкту контексту для заданого об' єкту можна викликавши з цього об' єкту функцію WINOLEAPI CoGetObjectContext ( [in] REFIID riid [out] LPVOID **ppv }; Перший параметр задає GUID прошеного інтерфейсу, реалізованого об' єктом контексту (IID_IobectContext IID_IObjectContextInfo, IID_IObjectContextActivity, IID_IContextState). У іншому параметрі повертається адреси покажчика на запрошений інтерфейс. Використовуючи ці інтерфейси об' єкт може не лише упізнати свій поточний стан, але і змінити його. Розглядати ці інтерфейси тут мі не будемо, оскільки спочатку необхідно вивчити сервіси, для використання яких ці інтерфейси і розроблені. Термін контекст використовується ще в одному сенсі - безліч об' єктів, що живуть в одному апартаменті і мають однакові вимоги до середовища виконання. Контекст об' єкту визначається при його активації і залежить як від атрибутів, приписаних відповідному класу, так і від контексту об' єкту, що ініціював активацію цього об' єкту (активатора). Якщо активований об' єкт є екземпляром неконфігурованого класу, то можливі два варіанти. Якщо активований об' єкт і його активатор живуть в одному апартаменті, то активований об' єкт поміщається в контекст активатора. У осоружному випадку активований об' єкт поміщається в так звань контекст за умовчанням свого апартаменту. Для об' єктів розміщених в контексті за умовчанням, недоступні ніякі сервіси. Отже, кожен об' єкт в COM+ живе в деякому контексті. Різні контексти не перетинаються один з одним і не перетинають межі апартаментів. Поняття контексту тісно пов'язане з поняттям перехоплення. Саме механізм перехоплення забезпечує облік семантики, визначеної при завданні атрибутів компонента. Don Box в статті ''Windows 2000 Brings Significant Refinements to the COM(+) Programming Model'', Microsoft System Journal, May 1999, так описує схему перехоплення 1. Компонент описує свої вимоги використовуючи атрибути. 2. Під година створення об' єкту система перевіряє - чи виконується активатор (код, CoCreateInstance, що викликав) в середовищі сумісною з конфігурацією класу ? 3. Якщо відповідь на попереднє питання позитивна, то перехоплення не потрібне, і CoCreateInstance повертає прямий покажчик на об' єкт. 4. Інакше CoCreateInstance передає управління середовищу, сумісному з вимогами класу, створює там об' єкт і повертає проксі. 5. Цей проксі забезпечує сумісність середовища виконання з вимогами класу, виконуючи певні дії до і після шкірного виклику методу. Фактично поняття перехоплення з'явилося навіть раніше MTS (Microsoft Transaction Server). У приведену вище схему повністю укладається процес створення нового екземпляра класу із заданою потоковою моделлю у рамках COM. Взагалі, Don Box називає принцип перехоплення наріжним каменем сучасного COM програмування. У COM маршализация покажчиків на інтерфейс вимагалася при виклику через кордон апартаменту. У COM+ така маршализация вимагається при виклику через кордон контексту. Відповідно до принципу перехоплення тільки у рамках одного контексту можна використовувати прямі покажчики на інтерфейс. Як і в COM маршализация і демаршализация покажчиків на інтерфейс виконується автоматичний при створенні, активації об' єкту і при виклику функцій, що повертають покажчики на інтерфейс. У інших випадках, для отримання покажчика на інтерфейс об' єкту з іншого контексту необхідно явним чином виконати процедури маршализации і демаршализации покажчика на інтерфейс. Для цього можна використовувати функції CoMarshalInterface і CoUnmarshalInterface. Для деякої оптимізації цього процесу можна проектувати об' єкти з FTM і використовувати GIT, як це було в COM. Синхронізація Раніше вже упоминалаось, що при програмуванні в COM+ рекомендується вибирати для нових класів потоковыю модель ThreadingModel = Neutral. Усі екземпляри такого класу розміщуватимуться в одному апартаменті NA. Основна перевага цього апартаменту полягає в тому, що він не має пов'язаних з ним потоків, і потік з будь-якого іншого апартаменту, що зробив виклик методу об' єкту з NA, тимчасово покидає свій апартамент і виконує код викликаного методу. Відсутність перемикання потоків істотно знижує витрати на виклик. Проте, необхідно потурбуватися про синхронізацію. Сам апартамент NA ніяк не обмежує можливість паралельного виклику одного і того ж методу одного і того ж об' єкту з NA. У рамках COM синхронізація забезпечувалася або написанням потоко-безопасного коду (наприклад, шляхом використання критичних секцій), або об' єкт поміщався в STA апартамент. У COM+ з' являється нова можливість - декларація необхідності синхронізації шляхом завдання потрібного значення для відповідного атрибуту. Сама синхронізація забезпечується через механізм, заснований на понятті активність. Активність - множина з декількох контекстів, які можуть знаходитися не лише в різних апартаментах, але і в різних процесах і навіть на різних машинах. Будь-який контекст входити не більше ніж в одну активність. Можуть бути контексти, що не входять ні в одну активність. Включення контексту в деяку активність визначається атрибутом Synchronization об' єктів, що входять в контекст. У таблиці 3.1 приведені можливі значення цього атрибуту і їх вплив на входження контексту, якому належатиме об' єкт, що активується, в ту або іншу активність. При призначенні значення атрибуту Synchronization необхідно враховувати деякі обмеження. Саме, якщо потокова модель класу ThreadingModel=Apartment, або цей клас підтримує активацію із споживи, або він використовує сервіс транзакцій, необхідно вибрати для значення атрибуту синхронізації або REQUIRED, або REQUIRES_NEW. Тепер розглянемо власне механізм синхронізації. На рівні процесу активність блокується як тільки поступивши виклик до одного з об' єктів одного з її контекстів. Тільки після виконання цього виклику блокування знімається і інший виклик може увійти до активності. Для запобігання deadlock використовується відстежування ланцюжків викликів. Кожен виклик отримує унікальний ідентифікатор (GUID), і усі виклики, зроблені для виконання цього виклику, отримують тій же ідентифікатор. Виклик, ідентифікатор якого співпадає з ідентифікатором виклику, що блокував активність, бажає увійти в активність, входити в неї. На шкода, описів механізм синхронізації не вирішує проблему синхронізації повністю. Блокування при виклику об' єкту з деякої активності встановлюється на рівні процесу. Це означає, що у разі, коли активність включає контексти з декількох процесів, виклики, спрямовані в контексти цієї активності, але що належать різним процесам, можуть виконуватися паралельно. Це може привести до deadlock. Приклад наведень в згаданій вище статті Don Box. Нехай потік X викликає об' єкт A, потік Y викликає об' єкт B, об' єкти A і B належать одній активності, але різним процесам. У цьому випадку виклики можуть виконуватися паралельно. Нехай тепер об' єкт A викликав об' єкт B, а об' єкт B викликав об' єкт A. Виникає deadlock, оскільки ці виклики належать різним ланцюжкам викликів і блокують один одного. Рецепт наступний. Не слід різним клієнтам користуватися одними і тими ж об' єктами -серверами. Кожен клієнт повинні активувати для собі новий об' єкт -сервер. Розділяти слід тільки дані. Їх узгодженість забезпечуватиметься сервісом транзакцій. Розглянувши поняття синхронізації можна поставити питання про роль апартаментів в COM+. Як говорити Don Box, їх роль різко обмежена. Це прив'язка потоків до контекстів і тільки. Дійсно, до шкірного апартаменту окрім NA прив'язаний один (у випадку STA) або декілька (у разі MTA) потоків. Ці потоки можуть викликати будь-який метод будь-якого об' єкту відповідного апартаменту. Методи об' єкту з NA взагалі можуть бути викликані з будь-якого потоку. Механізм синхронізації у COM+ визначає коли потік з апартаменту може викликати метод об' єкту, що живе в деякому контексті цього апартаменту. |