1. Навіщо використовувати компоненти?

Крім того, що “зараз все програмують з використанням компонентів”, причин для цього може бути декілька. Застосувавши корисні компоненти сторонніх виробників (а таких компонентів на сьогоднішній день вже досить багато), ви зможете помітно розширити можливості вашої програми, не витрачаючи часу на розробку власного коду.


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


У вас також може з’явитися потреба скористатися деякими новими компонентами розширень операційної системи, таких як DirectX Media або ActiveX Data Objects. Або ваші колеги розробили компонент, який потрібно інтегрувати у ваше додатка для виконання всіх необхідних операцій. В якому випадку поставленої мети можна домогтися декількома способами. Один з найбільш швидких і простих способів ми покажемо на практиці в нашому тестовому додатку.



Рис. 1. Архітектура “Модель – уявлення – управління”, використовувана в тестовому додатку.


2. Методи вбудовування компонентів


Щоб зрозуміти принадність використання MFC і директиви # import для впровадження компонентів, потрібно знати можливі шляхи вирішення цього завдання.


Наприклад, можна написати код, що використовує тільки можливості C + + і COM, який створить компонент і буде його використовувати; в цьому випадку вам потрібно в подробицях розібратися з концепцією COM-компонентів і написати пристойний шматок часто повторюваного і схильного помилок коду. Ви також можете використовувати і класи ATL, але, знову ж таки, крім знання COM вам доведеться вивчити також і ATL. Можна використовувати і можливість роботи з контейнерами ActiveX, закладену в майстер створення MFC-додатків, але якщо вам не вистачить можливостей, пропонованих меню Insert Object, вам доведеться заритися у вивчення коду підтримки OLE в MFC. Більш простий підхід – використовувати класи-оболонки компонентів, генеруються бібліотекою компонентів і елементів управління (Components and Controls Library). Але ці класи цілком залежать від підтримки автоматизації, яка є не у всіх компонентах. До того ж використання інтерфейсів автоматизації може спричинити зниження продуктивності.


Ми ж розглянемо підхід, що вимагає написання мінімальної кількості коду та мінімального знання COM. У ньому використовується комбінація можливостей MFC і директиви # import. В кінці ми розглянемо, як використовувати такий підхід для створення компонентів, програмного взаємодії з ними і організації взаємодії одних компонентів з іншими.


3. Основи COM


Якщо ви хочете ефективно використовувати COM-компоненти, вам потрібно розібратися в механізмі роботи та програмування таких компонентів.


У цій статті ми не зможемо детально про все це розповісти, так що давайте хоча б розберемося в базових положеннях. Перше, що потрібно усвідомити – це те, що можливості компонентів COM реалізуються за схемою клієнт-сервер. Додаток (Або інший компонент) отримують доступ до компоненту і використовують його можливості в ролі клієнта, а сам компонент виступає в ролі сервера. COM визначає правила взаємодії клієнта і сервера на рівні виконуваного коду, що і дозволяє клієнтам, написаним на одній мові, взаємодіяти з серверами, написаними на інших мовах. Клієнт взаємодіє з компонентом тільки за допомогою інтерфейсу. Компонент може мати будь-яку кількість інтерфейсів, і кожен інтерфейс може мати будь-яку кількість визначених у ньому методів. Можливості компонента повністю визначаються методами його інтерфейсів.


Всі інтерфейси COM-компоненти повинні бути успадковані від інтерфейсу IUnknown. Істотна відмінність між інтерфейсами полягає в тому, підтримує інтерфейс автоматизацію чи ні. Інтерфейси з підтримкою автоматизації успадковані від інтерфейсу IDispatch, який, у свою чергу, успадкований від IUnknown. Таке спадкування впливає на метод доступу до інтерфейсу з C + + та інших мов, а також визначає, чи доступний інтерфейс з таких мов, як JScript або VBScript, які не можуть безпосередньо маніпулювати покажчиками. Основне наслідок наявності автоматизації в інтерфейсу при використанні C + + – механізм визначення можливостей компонента (тобто, які методи реалізовані в даному інтерфейсі) під час виконання. Така гнучкість призводить до програшу в продуктивності, проте, вона сильно розширює сумісність компонента з додатками, написаними на інших мовах.


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


4. Типи компонентів


Одна з причин відсутності простого опису COM – наявність великої кількості “Стандартних” інтерфейсів. IUnknown і IDispatch – найбільш поширені, однак, є й інші. Одні визначають взаємодію компонента з контейнером, при розміщенні компонента в клієнтській області вікна додатка, аналогічно візуальним елементам управління. Інші – працюють аналогічно елементам управління, розміщених на Web-сторінках, які взаємодіють з браузером. В додаток до цього існує ще і плутанина в термінах: COM, ActiveX, OLE, елементи керування та компоненти: Щоб не плутатися у всіляких варіантах, введемо наступну класифікацію компонентів: невізуальні компоненти, візуальні компоненти (які ми будемо називати елементами управління) і компоненти, повідомляючі клієнта про настання подій. Як ви побачите, між цими спрощеними групами можливі перетину, і існує багато інших варіантів розбиття компонентів на групи в залежності від їх інтерфейсів.


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


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


Робота з візуальними компонентами, або елементами керування, кілька складніше, оскільки потрібно забезпечити контейнер, що містить компонент, деякої інформацією про компоненті, так як компонент стає дочірнім вікном контейнера. Потрібно також передавати деяку інформацію і від контейнера назад компоненту, оскільки останньому потрібно знати, як отрісовивать себе і як взаємодіяти з її утримує контейнером. У що наводиться прикладі використовуються деякі можливості класу CWnd бібліотеки MFC, автоматизують більшість необхідних операцій.


Візуальні COM-компоненти найчастіше називають керуючим елементами ActiveX або OLE (в цій статті ми використовуємо цей термін тільки по відношенню до компонентів, реагує на введення користувача). Прикладами візуальних компонентів можуть служити деякі специфічні елементи управління (наприклад, циферблат, шкала) або компоненти, що містять деякі графічні елементи, наприклад індикатор радара в симуляторі літального апарату.



Малюнок 2. Додаток DemoClient в роботі


Третій тип компонентів, про які піде мова в цій статті, – це компоненти, що повідомляють клієнта через точки взаємодії (connection points) про настання подій (наприклад, про натискання кнопки миші або про виконання певної обчислювальної задачі), або викликають певні методи клієнта для завершення виконання будь-яких завдань (в цьому випадку клієнт зазвичай теж є компонентом). У що наводиться прикладі ми побачимо, як використовувати компоненти в обох випадках.


Служба COM + Event Service в Windows 2000 вводить абсолютно нові можливості маршрутизації і обробки повідомлень від компонентів. У порівнянні з традиційним механізмом, що використовують для цих цілей точки взаємодії, нова модель більш пристосована до використання в розподілених обчисленнях і дозволяє відокремити компоненти від їх клієнтів при обробці подій. Подробиці цього механізму – це окрема тема для розмови. Ми повернемося до неї в наступних випусках нашого журналу.


5. Розміщення керуючих елементів за допомогою CWnd


При розміщенні елемента керування ActiveX клас CWnd з MFC виконує величезний шматок роботи самостійно. Написавши один рядок коду, можна попросити CWnd створити елемент управління і виконати всі необхідні дії для забезпечення його взаємодії з її утримує контейнером. Ще простіше цього домогтися за допомогою редактора ресурсів, вставивши елемент керування в потрібне діалогове вікно. Але можливо вам знадобиться використовувати елементи управління або компоненти в довільному місці вашого застосування, тому ми розглянемо варіант розміщення елемента управління в довільному вікні MFC.


Для додавання керуючого елемента ActiveX в вікно вашого застосування в Як дочірнього вікна створіть у класі батьківського вікна член типу CWnd і використовуйте для створення самого елемента функцію CWnd :: CreateControl () там, де це стане необхідно.


Ви можете створювати компоненти на різних стадіях створення вашого додатки, в залежності від типу компонента. Прості COM-компоненти (без користувальницького інтерфейсу) можна створювати відразу після ініціалізації бібліотеки COM за допомогою виклику AfxEnableControlContainer (). Майстер створення додатка поміщає цей виклик у функцію InitInstance () вашого класу, наследованного від CWinApp. Якщо ж ваш компонент має користувальницький інтерфейс або використовує деякі більш складні можливості ActiveX / OLE, потрібно почекати, поки не будуть проініціалізувати ще кілька об’єктів оточення MFC. Класи MFC, успадковані від CView, мають віртуальну функцію OnInitialUpdate (), яку можна перевизначити для виконання необхідних дій безпосередньо перед відображенням користувальницького інтерфейсу. Це найкраще місце для ініціалізації ваших візуальних компонентів, що гарантує, що MFC вже завершила створення середовища OLE.


CWnd :: CreateControl () має набір параметрів, повністю визначають створюваний компонент і несуть інформацію про клієнтському вікні, з тим, щоб Windows могла правильно ініціалізувати містить керуючий елемент вікно. Перший параметр – це CLSID або ProgID, що ідентифікує керуючий елемент, який необхідно створити. CLSID і ProgID – це ідентифікатори, які компоненти поміщають в реєстр Windows при своїй інсталяції. CLSID – це GUID (128-бітове значення), що представляє клас компонента, а ProgID – текстове ім’я, наприклад “DomeWorks.DemoController”, яке пов’язане з CLSID в реєстрі. Функція CreateControl () має також інші параметри, що включають назву вікна, прапори типу вікна, координати прямокутника в системі координат клієнта, в якому має бути створено вікно, батьківське вікно і ідентифікатор керуючого елемента. Кілька перевантажених варіантів цієї функції видозмінюють ці параметри і мають значення за замовчуванням для додаткових параметрів. Як правило, можна скористатися значеннями за замовчуванням.


6. Використання директиви # import


Директива # import, що вперше з’явилася в Visual C + + 5.0, – могутній інструмент для роботи з компонентами і їх інтерфейсами. Її використання інтуїтивно більш зрозуміло і вимагає написання набагато меншого обсягу коду, ніж при використанні традиційних можливостей C + + / COM. Директива читає бібліотеку типів компонента або групи компонентів і створює вихідний код класів-оболонок з покажчиками на інтерфейси (smart pointers), реалізовані та експортуються з цих компонентів.


Ці класи є класами-шаблонами, заснованими на шаблоні _com_ptr_t <>, який створює клас-оболонку для COM-інтерфейсу. Вони звільняють вас від необхідності обліку посилань на інтерфейс, обов’язкового при звичайних умовах. Вони також створюють оператори для роботи з інтерфейсом, використання яких інтуїтивно більш зрозуміло, і дозволяють методам інтерфейсу повертати значення. У що наводиться прикладі ви зможете побачити все це в дії.


Виклики методів COM завжди повертають результат типу HRESULT, який є набором прапорів, які містять ознаки успішності виконання методу та іншу інформацію, яку розробник компонента побажав закодувати в розрядах HRESULT. Оболонка інтерфейсу перехоплює значення, що повертається методом, і, якщо воно сигналізує про помилку, породжує виключення типу _com_error зі значень, що містяться у результаті методу.


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


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


Клас-оболонка дозволяє вам отримувати доступ до властивостей інтерфейсу так, як якщо б вони були членами самого класу-оболонки. При традиційному підході, застосовуваному при програмуванні COM на C + +, для отримання значення властивості вам потрібно викликати метод get_XXX (), де XXX – потрібне властивість. Аналогічно, для зміни значення властивості потрібно викликати метод put_XXX (). За допомогою інтелектуальних покажчиків, використовуваних в класі-оболонці, ви можете писати програму так, як якщо б ви посилалися або змінювали значення властивості безпосередньо; турботу про виклики get_ і put_ бере на себе клас-оболонка.


Наприклад, якщо без використання класу-оболонки ви використовуєте приблизно такий код:

HRESULT hr;
int i1, i2, result;
hr = m_pInterface->get_IntMember(&i1);
if (FAILED(hr))/ / Зробити те, що потрібно/ / Звільнити інтерфейси/ / Перервати виконання
hr = m_pInterface->CalcOtherInt(&i2);
if (FAILED(hr))/ / Зробити те, що потрібно/ / Звільнити інтерфейси/ / Перервати виконання
hr = m_pInterface->AddInt(i1, i2, &result);
if (FAILED(hr))/ / Зробити те, що потрібно/ / Звільнити інтерфейси/ / Перервати виконання

то за допомогою класів-оболонок те ж саме можна виконати ось так:

int i = 5;
try {
int result = m_pSmartPtr->AddInt(m_pSmartPtr->GetInt(),i);
}
catch (_com_error &e)
{/ / Зробити те, що потрібно
}

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


7. Компонування тестової програми


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


У нашій програмі використовується концепція “модель – представлення – управління “: програма складається з трьох модулів (у нашому випадку – COM-компонентів), що виконують основні функції додатка. Перший компонент – “Модель” – виконує основну корисну обчислювальне навантаження, перетворюючи дані, що вводяться в результат. Другий компонент – “уявлення” – займається відображенням роботи додатку. Компонент “управління” забезпечує введення даних. Така концепція покладена в основу багатьох додатків, і модель додатка MFC “Документ – вистава” – приватний варіант використання того ж підходу.


У що наводиться прикладі компоненти інтегровані в MFC-додаток з використання можливостей MFC і директиви # import. Додаток створює анімований компонент подання, швидкість анімації і колір фону в якому контролюються окремим компонентом з керуючими елементами. “За кулісами” третій компонент оновлює дані компонента уявлення, переводячи установки елементів управління в значення параметрів компонента уявлення. Клієнтське додаток, що містить компоненти, виконує роль посередника у взаємодії компонентів, показуючи, як можна безпосередньо керувати компонентами. Основні зв’язки між компонентами і клієнтським додатком показані на рис.
1.


Створення нашого застосування почнемо з майстра додатків MFC для створення програми, яка буде містити наші компоненти. Назвемо її DemoClient. Все, що нам потрібно змінити в параметрах, пропонованих майстром – це вибрати однодокументних інтерфейс для нашої програми. У цьому випадку для додатка буде використовуватися модель “Документ – вистава”, але ми внесемо всі необхідні зміни безпосередньо в клас CDemoClientView.


7.1 Про компонентах


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



Рис. 3. Нові повідомлення і події вікна


Компонент, що виконує роль моделі, називається DemoModel. Він виконує трансляцію установок компонента управління у команди для компонента представлення (DemoView). В ньому реалізований інтерфейс, побудований на точках взаємодії, що дозволяє компоненту-моделі після початкової ініціалізації управляти компонентом уявлення напряму, без посередництва клієнтського додатка. Компонент DemoView використовує ATL Composite Control для розміщення в ньому компонента DirectAnimation. Керуючий елемент використовується тільки як контейнер для компонента DirectAnimation, який заповнює клієнтську область компонента DemoView, виконуючи всі промальовування на підставі встановлених при допомоги його інтерфейсу параметрів.


Компонент DemoController – також складовою ATL-компонент. Він містить набір стандартних елементів управління Windows: повзунок, випадаючий список і кнопку. Крім цього він є джерелом подій, які захоплюються клієнтським додатком і передаються у вигляді команд компонентам моделі і представлення.


7.2 Реєстрація компонентів


Перед використанням компонентів їх потрібно зареєструвати в системі. Зазвичай реєстрація виконується автоматично при інсталяції програми або на етапі збірки програми при її розробці в Visual C + +. Якщо ви скомпілюєте проект, містить демонстраційні компоненти, Visual C + + сам зареєструє компоненти на заключному етапі збірки додатку. Однак, якщо вам коли-небудь знадобиться самостійно зареєструвати компонент, що міститься в бібліотеці DLL, вам потрібно буде просто запустити утиліту Windows regsvr32.exe і вказати в командному рядку шлях до DLL-файлу. Наприклад, якщо ви візьмете файл DemoComponents.dll і розташуйте його в папці C: MyComponents, ви зможете зареєструвати його, набравши в командному рядку

c:windowssystem egsvr32 c:mycomponentsdemocomponents.dll

(Ми маємо на увазі, що Windows встановлена ​​в каталог C: Windows).


Можна також скасувати реєстрацію компоненту і видалити записи про нього з реєстру, запустивши regsvr32.exe з ключем-u, предваряющим шлях до бібліотеки, містить компоненти.


Якщо ви вирішили повторити наше дослідження демонстраційної програми на своїй машині, то зараз слід відкомпілювати проект DemoComponents, щоб створити та зареєструвати в системі використовувані компоненти.


7.3 Імпортування бібліотеки типів


Перший крок інтеграції COM-компонента в додаток: забезпечити додаток достатньою кількістю інформації для створення компонента. Ви можете зробити це, включивши файл заголовків, що містить глобально унікальні ідентифікатори компонента (GUID) і визначення класів інтерфейсів. Але все ж краще використовувати по відношенню до бібліотеці типів директиву # import, яка зробить те ж саме, але крім цього згенерує ще й класи-оболонки для інтерфейсів.


Єдиним складним моментом у цьому процесі може стати пошук компонентів або бібліотек, які потрібно імпортувати. Це не складе труднощів, якщо ви самі розробляли компонент, оскільки бібліотека є частиною вашого проекту. А от якщо вам, наприклад, потрібно імпортувати бібліотеку типів Microsoft Outlook, знайти її не так просто. Один з найбільш простих методів пошуку потрібної інформації полягає у використанні програми OLE / COM Object Viewer, яка входить в комплект поставки Visual C + +. Ця програма допоможе вам виявити компоненти, інтерфейси і бібліотеки типів, а також вкаже шлях до файлів бібліотек, якщо тільки компоненти були зареєстровані в системі. Щоб імпортувати інформацію про наших компонентах з бібліотеки типів, потрібно додати в початок файлу заголовків DemoView.h рядок

#import "..DemoComponentsDemoComponents.tlb" no_namespace

Для вказівки шляху використовуються ті ж угоди, що для директиви # include, і в нашому випадку мається на увазі, що папка проекту DemoComponents міститься в тому ж каталозі, що і папка проекту DemoClient. Бібліотеки типів містяться або в окремому файлі TLB, або в бібліотеці DLL. Атрибут no_namespace вказує компілятору ігнорувати будь-яке визначення простору імен з бібліотеки. Визначення в більшості бібліотек полягають у свої простори імен з тим, щоб уникнути конфліктів імен з іншими визначеннями в поточному просторі імен. Директиву no_namespace можна використовувати, якщо ви впевнені в відсутності конфліктів визначень, в іншому випадку можна використовувати директиву rename_attribute для перейменування конфліктуючих визначень. Детальніше про всі можливості можна прочитати в розділі MSDN, присвяченому
#import.


Після того, як ви імпортуєте інформацію з бібліотек типів і откомпіліруете свій проект, компілятор спробує прочитати інформацію з зазначеного вами файлу. Якщо ця операція виконається успішно, він згенерує класи оболонки, засновані на шаблоні _com_ptr_t <>, для інтерфейсів, визначених у бібліотеці типів. Ця інформація буде розміщена в двох файлах: файлі заголовків з розширенням TLH і файлі реалізації з розширенням TLI. Ці файли неявно включаються в ваш проект в області видимості директиви # import, так що ви можете використовувати згенеровані класи. Ви можете також явно підключити файл TLH, якщо хочете, щоб згенеровані класи відображалися в вікні ClassView. Якщо ви використовуєте Visual C + + 6.0 або більш пізній, інформація про згенерованих класах буде збережена в проекті. У результаті про ці класах дізнається IntelliSense і зможе показувати спливаючі підказки для методів і властивостей членів інтерфейсів і для параметрів методів під час їх введення. Він також додає деякі визначення в згенерований TLH-файл заголовків з тим, щоб ви могли використовувати імена компонентів і інтерфейсів замість їх GUID для таких методів, як CWnd :: CreateControl ().


Стосовно до наших демонстраційним компонентам, після компіляції проекту будуть створені класи-оболонки для інтерфейсів IDemoModelPtr, IDemoViewPtr, IDemoControllerPtr, IDemoModelConnectionPtr і _IDemoControllerEventsPtr. Назва кожного згенерованого класу містить назву інтерфейсу, до якого додано закінчення “Ptr”. Інтерфейси IDemoModelConnection і _IDemoControllerEvents – Вихідні (outgoing), так що це просто базові класи, у використанні яких безпосередньо особливого сенсу немає. Далі ми розглянемо, як за допомогою інших трьох класів-оболонок отримати доступ до методам інтерфейсів, і як використовувати два, що залишилися інтерфейсу.


7.4 Визначення членів CDemoClientView


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


Спочатку додамо в клас CDemoClientView дві змінні типу CWnd, які будуть містити реалізації наших двох візуальних компонентів: m_viewWnd і m_controllerWnd. Далі, додамо по одній змінній для кожного класу-оболонки інтерфейсу, що дозволить отримувати доступ до методів у відповідь на повідомлення про події під час виконання програми. Додамо по одній такої змінної для інтерфейсів IDemoModel, IDemoView і IDemoController. Не забудьте додавати закінчення “Ptr” до назв інтерфейсів, щоб отримати назви класів, згенерованих директивою # import. Решта членів, які необхідно додати, відносяться до механізму точок взаємодії. Додамо ще дві змінні: інтелектуальний покажчик на інтерфейс IConnectionPoint і ключик типу DWORD. Ось ці визначення:

class CDemoClientView : public CView
{
….
public:CWnd m_viewWnd; / / контейнер для компонента уявленняCWnd m_controllerWnd; / / контейнер для компонента управлінняIDemoModelPtr m_pModel; / / інтелектуальний покажчик на IDemoModelIDemoViewPtr m_pView; / / інтелектуальний покажчик на IDemoViewIDemoControllerPtr m_pController; / / інтелектуальний покажчикIConnectionPointPtr m_pCP; / / інтелектуальний покажчик на IConnectionPointDWORD m_dwCookie; / / містить ключик для ідентифікатора/ / Точки взаємодії …
}

Далі, перевизначити віртуальну функцію OnInitialUpdate () класу CDemoClientView. Це можна зробити, натиснувши праву кнопку миші у вікні ClassView і вибравши команду Add Virtual Function, а далі вибравши елемент OnInitialUpdate () з запропонованого списку функцій. Після цього натисніть кнопки Add і Edit, щоб додати потрібний код і переміститися в визначення функції, щоб внести необхідні доповнення. Тут, після виклику функції OnInitialUpdate () базового класу, слід вставити код створення наших компонентів.


7.5 Створення компонентів


Тепер ми можемо приступити до створення самих компонентів. Спочатку створимо візуальні компоненти, скориставшись тільки що створеними членами типу CWnd. Для цього застосуємо функцію CWnd :: CreateControl (). Для вказівки CLSID компонентів використовуємо результати роботи компілятора при виконанні директиви # import: ключове слово __ uuidof () з ім’ям класу.


Ось код створення візуальних компонентів:

 / / Створення керуючих елементів ActiveX
m_viewWnd.CreateControl(__uuidof(DemoView),"View", WS_VISIBLE,
CRect(0,0,300,200),this,101);
m_controllerWnd.CreateControl(__uuidof(DemoController),"Controller",
WS_VISIBLE, CRect(0,201,300,350), this, 102);

Якщо на цьому етапі відкомпілювати проект і запустити програму DemoClient, вона буде виглядати приблизно так, як показано на рис. 2. Головне вікно містить клієнтську частину, в якій розташовані компоненти представлення і відображення. Можна навіть скористатися елементами керування, але це ні до чого не приведе, оскільки ми ще не прив’язали їх зміна до чого-небудь.


Наступний крок – створення компонента-моделі. У даному випадку воно виконується дещо простіше, оскільки компонент не вимагає наявності вікна, яке б його містило. Для створення компонента можна використовувати функцію-член CreateInstance () згенерованого класу-оболонки, передавши їй CLSID компонента, який потрібно створити. Ця операція пройде успішно тільки в разі, якщо в зазначеному компоненті реалізований інтерфейс, клас-оболонка для якого використовується. В іншому випадку інтелектуальний покажчик отримає значення NULL. Ось як ми створюємо цей компонент:

/ / Створення компонента-моделі за допомогою класу-оболонки
m_pModel.CreateInstance(__uuidof(DemoModel));

Тепер, коли ми створили всі компоненти, нам необхідні покажчики на їх інтерфейси, щоб ми могли викликати методи цих інтерфейсів і виконувати потрібні дії. Що стосується компонента-моделі, то у нас вже є те, що потрібно: компонент був створений за допомогою функції CreateInstance () над самим інтелектуальним покажчиком, так що покажчик уже пов’язаний з компонентом через інтерфейс, який він представляє (в даному випадку IDemoModel). Інші два компонента були створені за допомогою класу CWnd, тому їх потрібно пов’язати з інтелектуальними покажчиками окремо. Завдяки спеціальному члену класу CWnd і оператору зв’язування класу оболонки ця операція теж виконується достатньо просто.


Щоб це зробити, потрібно результат виконання функції CWnd :: GetControlUnknown () передати оператору зв’язування класу-оболонки. Функція GetControlUnknown () повертає покажчик на інтерфейс IUnknown керуючого елемента ActiveX, що міститься в CWnd. Оператор зв’язування отримує цей покажчик та самостійно викликає функцію QueryInterface (), запитуючи покажчик на інтерфейс того ж типу, який представляє клас-оболонка. При успішному виконанні інтелектуальний покажчик отримає правильне значення покажчика на інтерфейс потрібного типу. Починаючи з цього моменту можна використовувати методи інтерфейсу, до тих пір, поки вказівник не звільнить інтерфейс перед своїм знищенням. У разі неуспіху виклику QueryInterface () покажчик отримає значення NULL.


7.6 Створення точок взаємодії


Раніше ми згадували, що точки взаємодії можна використовувати для повідомлення компонентом клієнта про настання деяких подій або запиту до клієнтові на виконання деяких дій. Проте їх можна використовувати і для забезпечення взаємодії між двома компонентами в одному або різних контейнерах.


Компонент, що надає точки взаємодії, повинен мати інтерфейс IConnectionPointContainer. У своїй реалізації цього інтерфейсу компонент вказує, який саме інтерфейс є інтерфейсом точок взаємодії. Клієнт повинен створити реалізацію цього інтерфейсу і передати компоненту посилання на цю реалізацію. Після цього компонент може викликати методи інтерфейсу точок взаємодії. Для реалізації інтерфейсу взаємодії клієнт повинен знати, які методи і властивості цей інтерфейс надає. Зазвичай клієнтові доступна ця інформація, оскільки компонент визначає інтерфейс точок взаємодії в своїй бібліотеці типів. Розробник клієнта зазвичай користується цією інформацією для того, щоб визначити, які методи і властивості він повинен реалізувати для використання точок взаємодії.


Перша точка взаємодії, яку ми будемо використовувати – інтерфейс IDemoModelConnection. Він визначений в компоненті DemoModel і є вихідним керуючим інтерфейсом, за допомогою якого компонент-модель може напряму управляти компонентом уявлення. Керуючий елемент DemoView створює реалізацію цього інтерфейсу, після чого компонент DemoModel може викликати методи цього інтерфейсу. По відношенню до загального опису точок взаємодії DemoView є клієнтом, а DemoModel – компонентом. Наступний код показує, як підключаються точки взаємодії:

 / / Створення точки взаємодії між поданням і моделлю
IConnectionPointContainerPtr pCPC = m_pModel;
IUnknownPtr pUnk = m_pView;
if (pCPC != NULL)
{
pCPC->FindConnectionPoint(__uuidof(IDemoModelConnection), &m_pCP);
if (m_pCP != NULL)
{
m_pCP->Advise(pUnk, &m_dwCookie);
}
}

Тут ми знову використовували оператор зв’язування класу-оболонки для безпосереднього виклику QueryInterface () для інтерфейсу IConnectionPointContainer. Те ж саме ми робимо для отримання покажчика IUnknown на керуючий елемент уявлення. При успішному завершенні ми викликаємо метод FindConnectionPoint () інтерфейсу IConnectionPointContainer для компонента моделі щоб дізнатися, чи підтримує він інтерфейс точок взаємодії IDemoModelConnection. Якщо це так, то покажчик IUnknown передається компоненту уявлення, а у відповідь приймається значення ключика для цієї взаємодії. Після установки взаємодії компонент DemoModel може викликати будь-який метод інтерфейсу IDemoModelConnection компонента уявлення. Якщо ви заглянете в код компонента, то побачите, що модель створює таймер і періодично викликає метод Update () для поновлення кута повороту представлення згідно з новими показаннями швидкості і часу.


7.7 Синхронізація параметрів


Останнє, що залишилося зробити – синхронізувати параметри компонента управління з компонентами представлення і моделі. Для цього потрібно ввести зміни в функцію OnInitialUpdate ():

 / / Ініціалізація параметрів компонентів
m_pView->bkgndColor = m_pController->bkcolor;
m_pModel->ChangeSpeed(m_pController->speed);

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


Це все, що вам потрібно змінити в функції OnInitialUpdate (), за одним винятком. Досить корисно укласти наведені вище рядки в блок try: catch, оскільки класи-оболонки з інтелектуальними покажчиками повертають результат викликів методів інтерфейсів через параметр HRESULT. Якщо його значення вказує на помилку, клас-оболонка породжує виключення типу _com_error. У випадку виникнення помилки її краще обробити всередині блоку обробки виключень. Для цього потрібно дозволити обробку виключень для вашого проекту. У проектах MFC вона дозволена за замовчуванням, а ось в проектах ATL – немає.


7.8 Обробка подій від компонентів


Елементи керування ActiveX можуть бути джерелами подій або повідомлень клієнтові про виникнення ситуації, на яку клієнтові слід звернути увагу. Насправді події від клієнта реалізовані за допомогою інтерфейсів точок взаємодії. Однак такі події використовують передавальний інтерфейс, успадкованих від IDispatch. Це дозволяє використовувати їх клієнтам, у яких реалізована автоматизація, наприклад сценаріями Web-сторінок.


Для підключення механізму подій можна використовувати таку ж процедуру, як при підключенні точок взаємодії IConnectionPointContainer. Але оскільки інтерфейс подій є передавальним, можна піти більш простим шляхом. Назва “передавальний інтерфейс” виникло через те, що інтерфейс містить механізм передачі викликів методів відповідним обробникам під час виконання без необхідності явної реалізації всіх методів інтерфейсу. Це основний метод обробки подій, і клас MFC CCmdTarget, від якого успадковував CWnd, реалізує інтерфейс IDispatch і може передавати виклики функції-обробника, визначеним у карті подій.


Щоб використовувати цю можливість предків в класі CWnd, потрібно зробити 3 речі. Спочатку потрібно вказати, що ваш клас, успадкований від CWnd (в нашому випадку – CDemoClientView), має карту подій, додавши у файлі заголовків у визначення класу макрос DECLARE_EVENTSINK_MAP (). Далі, потрібно додати в файл реалізації (. cpp) код уловлювання подій (буде доречно розташувати його відразу після макросу карти повідомлень, згенерованого майстром створення додатка):

BEGIN_EVENTSINK_MAP(CDemoClientView, CView)
ON_EVENT(CDemoClientView, IDC_CONTROLLER, 1, OnSpeedChanged, VTS_NONE)
ON_EVENT(CDemoClientView, IDC_CONTROLLER, 2, OnColorChanged, VTS_NONE)
ON_EVENT(CDemoClientView, IDC_CONTROLLER, 3, OnStopStart, VTS_BOOL)
END_EVENTSINK_MAP()

Кожен макрос ON_EVENT () визначає передавальний метод, вказуючи його ідентифікатор, який був використаний у виклику CreateControl (), його DISPID, назву функції-обробника події і всі параметри, передані подією, якщо такі є. Перших дві події не передають параметрів, на що вказує константа VTS_NONE. Третє передає значення типу Boolean, вказує, що мається на увазі: запуск або зупинка.


Останній крок – додати код обробників, які ми визначили. Використовуйте для створення функцій спливаюче меню в ClassView або ClassWizard і добавте функції, наведені в лістингу 1. Як ви можете бачити, всі виклики методів класів з інтелектуальними покажчиками укладені в блоки try … catch. Код функцій досить зрозумілий. Глобальна функція dump_com_error () визначена для виведення повідомлення про помилку на підставі значення HRESULT.


Клас CDemoClientView є посередником між компонентом управління і компонентами моделі і представлення. Коли компонент управління повідомляє клас CDemoClientView про один з трьох певних подій, викликається відповідна функція. Залежно від події, клас може отримати від компонента управління значення параметра і викликати метод або змінити властивість компонентів моделі і представлення.


7.9 Очищення


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


Єдине, що потрібно зробити самостійно – звільнити інтерфейси точок взаємодії, які ми також створювали самостійно. Для цього потрібно, щоб компонент моделі звільнив своє посилання на інтерфейс компонента представлення за допомогою методу Unadvise () інтерфейсу IConnectionPoint. Саме для цього ми використовували інтелектуальний покажчик на цей інтерфейс і зберігали значення ключика, повернене методом Advise ().


Найкраще виконати ці операції тоді, коли вікно посилає повідомлення WM_DESTROY. Для створення обробника цієї події виберіть клас CDemoCleintView у вікні ClassView і виберіть із спливаючого меню операції Add Windows Message Handler (див. рис. 3). Із запропонованого списку виберіть повідомлення WM_DESTROY і натисніть кнопки Add й Edit. У тілі функції OnDestroy () додайте наступний код перед викликом CView :: OnDestroy () базового класу (а не після нього, де майстер додатка розташував коментар TODO):

   try
{
m_pCP->Unadvise(m_dwCookie);
}
catch (_com_error &e)
{
dump_com_error( e );
}
CView::OnDestroy();

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


Тепер ви можете відкомпілювати і запустити свій додаток (якщо ви вже створили і зареєстрували самі компоненти), змінювати параметри компонента управління і спостерігати за змінами в компоненті уявлення.


Лістинг 1. Ці функції-члени обробляють події від компонента DemoController, коли користувач використовує елементи управління

/ / У визначенні класу CDemoClientView
class CDemoClientView : public CView
{

BOOL OnStopStart(VARIANT_BOOL start);
BOOL OnColorChanged();
BOOL OnSpeedChanged();

}/ / У файлі реалізації CDEmoClientView
BOOL CDemoClientView::OnSpeedChanged()
{
try
{
short speed = m_pController->speed;
m_pModel->ChangeSpeed(speed);
}
catch (_com_error &e)
{
dump_com_error( e );
}
return TRUE;
}
BOOL CDemoClientView::OnColorChanged()
{
try
{
COLORREF color = (COLORREF)m_pController->bkcolor;
m_pView->bkgndColor = (long) color;
}
catch (_com_error &e)
{
dump_com_error( e );
}
return TRUE;
}
BOOL CDemoClientView::OnStopStart(VARIANT_BOOL start)
{
try
{
if (start)
m_pModel->Spin();
else
m_pModel->Stop();
}
catch (_com_error &e)
{
dump_com_error( e );
}
return TRUE;
}

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*