|
конспект лекцій (ТСПП). Конспект лекцій з дисципліни 07 технологія створення програмних продуктів напряму 050101 Компютерні науки
6.3. Інтерфейс COM - об' єктів. Фабрики класів
Тепер виникає важливе питання - як клієнт може створювати COM об' єкти CoBook і CoJournal?
Два важливі принципи COM: Відповідно до ідеології моделі COM, класи COM (коклассы) можуть реалізовуватися на різних мовах і повинні зберігатися в бінарному виді (dll або exe файл). Клієнт може бути реалізований на будь-якому з тих, що підтримують модель COM мові і, отже, повинен мати можливість створювати (активізувати) COM об'єкт не використовуючи яких-небудь специфічних для цієї мови методів, а за допомогою спеціального стандартного інтерфейсу.
Прозорість місця розташування сервера. Є три місця, де може розміщуватися COM сервер:
В процесі клієнта
Цей спосіб забезпечує найшвидший зв'язок клієнта і сервера (який реалізується у вигляді dll). Але є і проблеми. Наприклад, при помилці в сервері ''вилітає'' увесь процес (тобто і клієнт).
Поза процесом клієнта, але на одній з ним машині Це так назывемая локальний зв'язок (зазвичай використовується exe -сервер). Вона забезпечує надійність (при помилках в сервері клієнт не гине), але потрібна організація передачі даних між клієнтом і сервером через межі процесів. Це COM бере на себе, забезпечуючи кодування, передачу і декодування даних. При цьому створюються проксі об'єкт в процесі клієнта і заглушка в процесі сервера. Проксі імітує для клієнта сервер, а заглужка імітує для сервера клієнта. У зв'язку з цим код клієнта не залежить від того, де розташовується сервер. Усе перетворення даних і їх пересилка здійснюється парою прокси-заглушка. Комутація проксі і заглушки заснована на протоколі спрощеного видаленого виклику процедур (LRPC - Lightweight Remote Procedure Call).
На видаленій машині При цьому зв'язок найбільш повільний. Архітектура схожа на архітектуру, описану в попередньому пункті, тільки замість LRPC використовується RPC - протокол видаленого виклику процедур.
Помітимо, що клієнт не зобов'язаний знать, де саме розташований використовуваний сервер. Код клієнта не міняється при зміні місця розташування сервера. Більше того, сервер може розташовуватися в декількох місцях, і на прохання клієнта система (менеджер управління сервісом), може встановлювати зв'язок клієнта з найближчою до нього копією сервера.
Отже, потрібний стандартний інтерфейс для активації COM об' єкту. Такий інтерфейс в COM є і називається IClassFactory
((((визначений в ). Для шкірного кокласса, ''що живе'' в деякому сервері, в цьому ж сервері повинний ''жити'' клас що реалізовує так звану фабрику класу (чи об' єкт класу). Саме фабрика класу надає клієнтові інтерфейс
IClassFactory і більше ніяких інших інтерфейсів (окрім, природно, IUnknown, від якого IClassFactory породжений як і вусі інші COM інтерфейси).
Інтерфейс IClassFactory має два методи: За допомогою цього методу клієнт, що має покажчик на фабрику деякого класу, може створити будь-яке число екземплярів цього класу і отримати покажчик на бажаний інтерфейс цього класу. Для активації якого-небудь COM об' єкту потрібне розміщення сервера, в якому ''живе'' цей об' єкт, в оперативній пам' яті. Кожен об' єкт веде лічильник посилань на собі і автоматичний видаляє собі з пам' яті, коли посилань більше немає. Якщо немає активних об' єктів, ''що живуть'' в сервері, то сервер також може бути видалений з пам' яті. Але клієнт може цьому восприпятствовать, заблокувавши сервер в пам' яті для швидшої активації об' єктів в майбутньому. Для цього він і може викликати метод LockServer.
Далі для визначеності розглядатимемо побудову dll -сервера (сервера в процесі клієнта). Природно, цей вибір ніяк не позначиться на коді клієнта. Побудова локального сервера (сервер поза процесом клієнта, але на цій же машині) буде розглянуто пізніше.
Наступний код містить визначення фабрики класу для кокласса CoBook.
//////////////////////////////////////////////////
// // // // CoBookFactory.h: заголовний файл для класу CoBookFactory --
// // // // фабрика класу для класу CoBook
//////////////////////////////////////////////////
####ifndef _CoBookFactory_
####define _CoBookFactory_
####include "CoBook.h"
class CoBookFactory: public IClassFactory
{
public:
// // // // Конструктор і деструкція
CoBookFactory();
virtual CoBookFactory();
////////IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** pIFace);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
////////IClassFactory
STDMETHODIMP LockServer(BOOL fLock);
STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter
REFIID riid, void** ppv);
private:
ULONG m_refCount; // Лічильник числа сылок на об' єкт
};
####endif
Тут стандартним чином оголошені конструктор, деструкція, три методи інтерфейсу IUnknown і два методи інтерфейсу
IClassFactory. Семантика усіх цих методів вже була викладена раніше. Додатково треба пояснити сенс параметрів
методів LockServer і CreateInstance.
Параметр fLock набуває значення TRUE - заблокувати сервер, або FALSE - розблоковувати сервер. Сервер веде підрахунок усіх блокувань, встановлених усіма його клієнтами, і дозволяє вивантажити собі з пам' яті, якщо тільки не залишилося активних об' єктів і спільний рахунок блокувань в пам' яті дорівнює нулю.
Перший параметр методу CreateInstance використовується тільки при агрегації, яка обговорювалася раніше . Саме, якщо клас CoBook проектується з можливістю його агрегації в іншій COM клас (клас CoBook готовий надати можливість
іншому класу видавати інтерфейси класу CoBook за свої), то через параметр pUnkOuter (тип якого визначається в
як typedef IUnknown __RPC_FAR * LPUNKNOWN) він отримує покажчик інтерфейсу IUnknown того об' єкту, який
його зібрався агрегувати. Якщо CoBook не проектувався з можливістю агрегації (так і є в нашому випадку), то він чекає отримання в цьому параметрі значення NULL, інакше тут буде бачена помилка CLASS_E_NOAGGREGATION.
Другий параметр методу CreateInstance задає посилання на ідентифікатор бажаного інтерфейсу, а третій повертає запрошений інтерфейс (якщо він є).
Реалізація фабрики класу CoBookFactory для класу CoBook представлена нижче.
//////////////////////////////////////////////////
// // // // CoBookFactory.cpp: реалізація класу CoBookFactory --
// // // // фабрики класу для класу CoBook.
//////////////////////////////////////////////////
####include "CoBookFactory.h"
extern ULONG g_lockCount; // Лічильник блокувань сервера в пам' яті
extern ULONG g_objCount; // Лічильник активних об' єктів
//////////////////////////////////////////////////
// // // // Конструктор і деструкція
//////////////////////////////////////////////////
CoBookFactory::CoBookFactory()
{
m_refCount = 0;
g_objCount++;
}
CoBookFactory::CoBookFactory()
{
g_objCount--;
}
////////IUnknown
STDMETHODIMP_(ULONG) CoBookFactory::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) CoBookFactory::Release()
{
if (--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP CoBookFactory::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
{
****ppv = (IUnknown*) this;
}
else if(riid == IID_IClassFactory)
{
****ppv = (IClassFactory*) this;
}
else
{
****ppv = NULL;
return E_NOINTERFACE;
}
((((((((IUnknown*)(*ppv)) ->AddRef();
return S_OK;
}
////////IClassFactory
STDMETHODIMP CoBookFactory::CreateInstance(
LPUNKNOWN pUnkOuter, REFIID riid, void** ppv)
{
if(pUnkOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
CoBook* pBookObj = NULL;
HRESULT hr;
pBookObj = new CoBook;
hr = pBookObj ->QueryInterface(riid, ppv);
if (FAILED(hr))
delete pBookObj;
return hr;
}
STDMETHODIMP CoBookFactory::LockServer(BOOL fLock)
{
if (fLock)
++++++++g_lockCount;
else
--------g_lockCount;
return S_OK;
}
У конструкторі обнуляється лічильник посилань на фабрику класу - член класу m_refCount (як завжди при створенні будь-якого об' єкту COM) і збільшується на одиницю глобальний лічильник числа активних об' єктів цього сервера (g_objCount).
У деструкції на одиницю зменшується глобальний лічильник активних об' єктів цього сервера.
Реалізація методів інтерфейсу IUnknown стандартна і вже обговорювалася раніше.
Перейдемо тепер до обговорення реалізації методів інтерфейсу
IClassFactory і почнемо з методу CreateInstance.
По-перше, не передбачається агрегація класу CoBook. У зв'язку з цим видається помилка CLASS_E_NOAGGREGATION, якщо перший параметр не рівний NULL.
У наступних рядках коду і проявляється зв'язок цієї фабрики класу саме з класом CoBook. Створюється екземпляр класу
CoBook і проситися покажчик на деякий інтерфейс цього класу :
CoBook* pBookObj = NULL;
HRESULT hr;
pBookObj = new CoBook;
hr = pBookObj ->QueryInterface(riid, ppv);
При невдачі отримання інтерфейсу, створений об' єкт знищується.
У методі LockServer глобальний лічильник блокувань сервера в пам' яті збільшується або зменшується на одиницю в залежності від значення параметра fLock.
Абсолютно аналогічно визначається і реалізується фабрика класу CoJournalFactory для кокласса CoJournal.
//////////////////////////////////////////////////
// // // // CoJournalFactory.h: заголовний файл для класу
// // // // CoJournalFactory -- фабрики класу для класу CoJournal
//////////////////////////////////////////////////
####ifndef _CoJournalFactory_
####define _CoJournalFactory_
####include "CoJournal.h"
class CoJournalFactory: public IClassFactory
{
public:
CoJournalFactory();
virtual CoJournalFactory();
////////IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** pIFace);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
////////IClassFactory
STDMETHODIMP LockServer(BOOL fLock);
STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter
REFIID riid, void** ppv);
private:
ULONG m_refCount;
};
####endif
І тепер реалізація фабрики класу CoJournalFactory для класу CoJournal.
//////////////////////////////////////////////////
// // // // CoJournalFactory.cpp: реалізація фабрики класу CoJournalFactory
// // // // для кокласса CoJournal
//////////////////////////////////////////////////
####include "CoJournalFactory.h"
extern ULONG g_lockCount;
extern ULONG g_objCount;
//////////////////////////////////////////////////
// // // // Конструктор і деструкція
//////////////////////////////////////////////////
CoJournalFactory::CoJournalFactory()
{
m_refCount = 0;
g_objCount++;
}
CoJournalFactory::CoJournalFactory()
{
g_objCount--;
}
////////IUnknown
STDMETHODIMP_(ULONG) CoJournalFactory::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) CoJournalFactory::Release()
{
if (--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP CoJournalFactory::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
{
****ppv = (IUnknown*) this;
}
else if(riid == IID_IClassFactory)
{
****ppv = (IClassFactory*) this;
}
else
{
****ppv = NULL;
return E_NOINTERFACE;
}
((((((((IUnknown*)(*ppv)) ->AddRef();
return S_OK;
}
////////IClassFactory
STDMETHODIMP CoJournalFactory::CreateInstance(
LPUNKNOWN pUnkOuter, REFIID riid, void** ppv)
{
if(pUnkOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
CoJournal* pJournalObj = NULL;
HRESULT hr;
pJournalObj = new CoJournal;
hr = pJournalObj ->QueryInterface(riid, ppv);
if (FAILED(hr))
delete pJournalObj;
return hr;
}
STDMETHODIMP CoJournalFactory::LockServer(BOOL fLock)
{
if (fLock)
++++++++g_lockCount;
else
--------g_lockCount;
return S_OK;
}
Закінчуючи розмову про фабрику класу треба помітити, що для неї не треба задавати GUID.
Мова опису інтерфейсів і бібліотека типів
Тепер звернемося до питань про мовну незалежність і прозорість місця розташування. Як вже раніше згадувалося, це принципові для COM вимоги.
Перше означає, що як компоненти, так і клієнти, що використовують їх, можуть реалізовуватися на будь-кому підтримувальному COM мові програмування (зокрема, C++ і Visual Basic). Проте описані раніше інтерфейси (файли IPub.h, IBook.h і IJournal.h) представлені на C++. Це означає, що будь-який розробник, що бажає реалізувати ці інтерфейси у своєму коклассе повинний використовувати саме C++. неприємніше, що і будь-який клієнт, що бажає використовувати побудовані таким чином компоненти, також має бути написань на C++.
Прозорість місця розташування означає, що код клієнта не повинний мінятися поклад від того, де розміщений використовуваний клієнтом компонент (або це dll, що завантажується в адресний простір клієнта, або це exe -сервер, що виконується в іншому процесі на тій же машині, де виконується клієнт, або компонент встановлений на видаленому комп' ютері). Звичайно, клієнт може явно вказати, який тип сервера він бажає використовувати, але він може залишити вибір за системою, і тоді буде використовуватися тій сервер, зв'язок з яким для цього клієнта буде найбільш швидким. У разі виконання сервера і клієнта в різних процесах необхідно забезпечити передачу даних між цими процесами. Прозорість місця розташування означає що COM бере це на собі.
Що потрібне для забезпечення незалежності від мови і прозорості місця розташування? Треба описати усі інтерфейси і класи деяким стандартним чином, зробивши ці описи доступними компіляторам з усіх мов, підтримувальних COM. Це і забезпечить мовну незалежність. Для забезпечення прозорості місця розташування треба мати можливість автоматичний формувати посередників, що забезпечують взаємодію клієнта і сервера, що виконуються в різних процесах. У COM такий посередник на стороні клієнта називається проксі, а посередник на стороні сервера називається заглушкою. Сморід реалізуються у вигляді компонент, що завантажуються в адресні простори клієнта і сервера і забезпечують для останніх ілюзію безпосередньої взаємодії один з одним (у одному адресному просторі). Насправді проксі і заглушка реалізують видалений виклик процедур, використовуючи протокол LRPC або RPC відповідно при розташуванні на одній або різних машинах.
Отже, C++ не годитися для опису інтерфейсів у зв'язку з тим, що в цій мові багато неоднозначностей, які, звичайно успішно дозволяються компілятором з C++, але в яких не зможуть розібратися компілятори з інших мов. Потрібний спеціальна мова, на якій можна однозначно описати усі інтерфейси і класи. Це мова опису інтерфейсів IDL - Interface Definition Language, спочатку описів в специфікації DCE (Distributed Computing Environment - розподілене середовище обчислень) від Open Software Foundation і далі розширений Microsoft. На нім описуються усі коклассы і усі інтерфейси, що реалізовуються в них, які входитимуть в створюваний компонент. При цьому немає необхідності описувати раніше описані інтерфейси (наприклад, стандартні). Досить включити їх опису за допомогою ключового слова import. Разом з описом коклассов і інтерфейсів потрібно описати так звану бібліотеку типів. Саме ця бібліотека і вирішуватиме проблему мовної незалежності. Вона в бінарному виді зберігатиме інформацію про усіх інтерфейсах і класах цього компонента у формі, доступній для читання компіляторами з усіх підтримувальних COM мов.
Далі idl -файл з описом інтерфейсів, класів і бібліотеки типів транслюється транслятором MILD (Microsoft IDL) в сукупність файлів, що утримують оголошення на C++/C усіх інтерфейсів, визначення GUID для усіх інтерфейсів і класів коди проксі і заглушки, а також формується в бінарному виді бібліотека типів. Використовуючи ці файли можна побудувати dll проксі і заглушки. При реалізації клієнта або сервера на C++/C можна використовувати отримані файли з описами інтерфейсів і визначеннями GUID, при використанні інших мов програмування, використовуватиметься бібліотека типів.
Далі приведень файл PubInProcServerTypeInfo.idl, що дає описи на IDL інтерфейсів, класів і бібліотеки типів для
проекту PubInProcServer.
import "oaidl.idl";
////////IPub
[object
uuid(9A5DE9A0-7225-11d5-98C7-000001223694)
helpstring("Base publication")]
interface IPub: IUnknown
{
HRESULT SetTitle([in] BSTR bstrTitle);
HRESULT SetYear([in] int nYear);
HRESULT GetInfo([out, retval] BSTR* pbstrInfo);
};
////////IBook
[object
uuid(9A5DE9A1-7225-11d5-98C7-000001223694)
helpstring("Book")]
interface IBook: IPub
{
HRESULT SetAuthor([in] BSTR bstrAuthor);
}
////////IJournal
[object
uuid(9A5DE9A2-7225-11d5-98C7-000001223694)
helpstring("Journal")]
interface IJournal: IPub
{
HRESULT SetNumber([in] int nNumber);
}
[uuid(68A702C2-8283-11d5-98C7-000001223694)
version(1.0)
helpstring("PubInProcServer with TypeLib")]
library PubInProcServer
{
importlib("stdole 32.tlb");
[uuid(49F00760-7238-11d5-98C7-000001223694)]
coclass CoBook
{
[default] interface IBook;
};
[uuid(49F00761-7238-11d5-98C7-000001223694)]
coclass CoJournal
{
[default] interface IJournal;
};__
};
Видно, що цей файл дуже схожий на заголовний файл в C++. Основна відмінність - наявність атрибутів в квадратних дужках.
Конструкція import "oaidl.idl"; забезпечує імпорт ряду стандартних описів (у тому числі, інтерфейсу IUnknown). Далі йдуть опису усіх призначених для користувача інтерфейсів, що реалізовуються в компоненті,: IPub, IBook і IJournal.
Опису шкірного інтерфейсу передує сукупність атрибутів, що починається з атрибуту object. Саме цей атрибут говорити про ті, що далі йде опис інтерфейсу COM, а не інтерфейсу RPC, для описів яких і створювався спочатку IDL.
Атрибут uuid використовується для завдання GUID відповідного інтерфейсу.
Атрибут helpstring дозволяє приписати описуваному інтерфейсу деякий рядок, який зберігатиметься в бібліотеці типів і може використовуватися різними програмами для уявлення користувачеві інформації про семантику даного інтерфейсу.
При описі методів інтерфейсів використовуються атрибути in, out, retval, що дозволяють вказати напрям передачі значення параметра. Атрибут in призначається за умовчанням і означає, що значення цього параметра передається серверу.
Навпаки, атрибут out говорити про необхідність передачі значення параметра клієнтові. Атрибут retval означає, що для цей параметр є повертаним значенням для відповідної функції при використанні, наприклад, Visual Basic.
Вказівка цих атрибутів (можливі їх поєднання) оптимізує трафік в мережі при виконанні видаленого виклику процедур.
Після опису усіх інтерфейсів описується бібліотека типів (ключове слово library). Як атрибути для бібліотеки типів задаються унікальний ідентифікатор (GUID, який можна отримати за допомогою guidgen.exe), номер версії і helpstring.
Оператор importlib("stdole 32.tlb") має бути деремо серед усіх операторів бібліотеки. Він задає імпорт стандартній двійкової бібліотеки типів stdole32.tlb.
|
|
|