Створення додатків із застосуванням COM +. Частина 2, Різне, Програмування, статті

Частина 1


Зміст


Ця частина статті присвячена створенню і застосуванню COM-серверів, використовуваних спільно з Microsoft Component Services, званих іноді у вітчизняній літературі службами компонентів (само ж розширення COM, що дозволяє створювати додатки, що використовують Component Services, отримало назву COM +). В першій частині даній статті ми розглянули призначення служб компонентів Windows 2000 і особливості об’єктів COM +, а також створили приклад найпростішого об’єкта COM +, здійснює доступ до даних. У запропонованій вашій увазі другий частини даної статті ми розповімо про реалізацію розподілених транзакцій і про обробку подій, що виникають в об’єктах COM +.


Управління транзакціями


Щоб реалізувати комплексну (у загальному випадку – розподілену) транзакцію, зазвичай потрібне створення декількох об’єктів COM +, а також організація їх звернень один до одного.


Реалізація транзакцій


Створимо приклад програми COM +, який реалізує таку транзакцію, для чого додамо ще два об’єкти до вже наявного. Перший з об’єктів буде мати єдиним методом, що додає запис в таблицю, в якої реєструються замовлення (назвемо її OrderedProducts, і, перш ніж створювати сам об’єкт, створимо і цю таблицю). За своєю реалізації даний об’єкт схожий на об’єкт Stock_Data, Соза нами раніше. У другому з об’єктів ми реалізуємо комплексну транзакцію, яка буде складатися з наступних маніпуляцій:



Взаємодія частин цього додатка схематично зображено на рис. 1.

Рис. 1. Додаток, що реалізує розподілену транзакцію


Для реалізації подібної транзакції один з об’єктів повинен звертатися до двох дочірнім об’єктах. Перший з них – stock.Stock_Data, який реалізує друге і третє дію з наведеного списку, нами вже створений. Приступимо до створення об’єкта, що реалізує перша дія. Але перед цим, як було обіцяно, створимо в базі даних Northwind таблицю OrderedProducts, виконавши наступне SQL-пропозиція:



CREATE TABLE [dbo].[OrderedProducts] (
[Ord_ID] [int] IDENTITY (1, 1) NOT NULL,
[Address] [char] (40) NULL,
[OrderedItem] [char] (50) NULL,
[UnitPrice] [money] NULL,
[Quantity] [int] NULL
) ON [PRIMARY]


Зверніть увагу: у цій таблиці ми створили поле Ord_Id типу Identity (в Microsoft SQL Server так називаються поля, яким при створенні нового запису значення присвоюється автоматично; зазвичай дані такого типу використовуються у первинних ключах таблиць цієї СУБД). Трохи пізніше за допомогою цього поля ми продемонструємо, що саме відбувається в базі даних при скасуванні транзакції. Значення полів Address і Quantity будуть отримані з кліенского додатки, а полів Ordered_Item і UnitPrice – з таблиці Products.


Створивши таблицю, займемося створенням об’єкта COM +. Використовуючи вже описану раніше послідовність дій, створимо бібліотеку ORDERS.DLL, яка буде містити об’єкт COM + з ім’ям Orders_Data і значенням властивості Transaction model, рівним Requires a transaction.


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


Перший з цих методів, який ми назвемо Add_Order, матиме такі параметри:



Реалізація зазначеного методу має наступний вигляд:



procedure TOrders_Data.Add_Order(const Addr,
OrderedItem: WideString;
UnitPrice: Currency; Quantity: Integer);
begin
try
/ / Єднаймося з базою даних
ADOCOnnection1.Open;
/ / Текст запиту до бази даних
ADOCommand1.CommandText :=
INSERT INTO OrderedProducts
+ VALUES( + Addr + , + OrderedItem+,
+ FloatToStr(UnitPrice) + ,
+ IntToStr(Quantity) + );
/ / Виконуємо запит
ADOCommand1.Execute;
/ / Розриває з’єднання з базою даних
ADOConnection1.Close;
/ / Інформуємо служби компонентів про успішне
/ / Виконанні запиту
SetComplete;
except
/ / Розриває з’єднання з базою даних
ADOCOnnection1.Close;
/ / Інформуємо служби компонентів про невдалу спробу
/ / Виконання запиту
SetAbort;
/ / Передаємо виняток зухвалому додатком або об’єкта
raise;
end;
end;


Другий метод – Get_Order_List – повертає набір даних, що є результатом запиту до таблиці OrderedProducts:



function TOrders_Data.Get_Order_List: Recordset;
var
QRY : string;
begin
/ / Текст запиту
QRY := SELECT * FROM OrderedProducts ORDER BY Ord_ID;
try
/ / Створюємо набір даних
Result := CoRecordset.Create;
Result.CursorLocation := adUseClient;
/ / Відкриваємо його
Result.Open(QRY,ADOConnection1.ConnectionString,
adOpenStatic,adLockBatchOptimistic, adCmdText);
/ / Розриває з’єднання з базою даних
ADOCOnnection1.Close;
/ / Інформуємо служби компонентів про успішне
/ / Виконанні запиту
SetComplete;
except
ADOConnection1.Close;
/ / Інформуємо служби компонентів про невдалу спробу
/ / Виконання запиту
SetAbort;
/ / Передаємо виняток зухвалому додатком
raise;
end;
end;


Як і в попередньому випадку, нам слід послатися в секції uses на модулі ADODB, ADODb_TLB, ADOR_TLB.


Після компіляції та збереження проекту скопіюємо створену нами бібліотеку ORDERS.DLL на серверний комп’ютер і зареєструємо об’єкт Orders_Data в службах компонентів в тому ж додатку COMPlus_Demo, що і попередній компонент.


Створивши два об’єкти COM +, маніпулюють двома таблицями, приступимо до створення третього об’єкту, який виступає в ролі батьківського по відношенню до вже створеним об’єктах. Для цього створимо нову бібліотеку ActiveX (назвемо її PROC), а в ній – новий об’єкт COM + (назвемо його Processing). На відміну від двох попередніх, цей об’єкт буде спадкоємцем не класу TMtsDataModule, а класу TMtsAutoObject (для його створення слід вибрати значок Transactional Object на сторінці ActiveX вікна репозитарія об’єктів), оскільки даний об’єкт COM + не буде містити жодних компонентів доступу до даних.


При створенні зазначеного об’єкта нам слід обрати Requires a new transaction як значення властивості Transaction model. Це означає, що при зверненні до даного об’єкта для нього створюється власний контекст транзакції і що він не може виконуватися в контексті транзакції іншого об’єкта. Нагадаємо, що два раніше створених об’єкта мали інше значення цього параметра – Requires a transaction, що дозволяло виконувати їх у контексті транзакції об’єктів, які до них звертаються.


Додамо до бібліотеки типів новоствореного об’єкта три методи. Два з них – Get_OrderList і Get_ProductList – повертають набори даних, які є результатами запитів до таблиць OrderedProducts і Products відповідно. Як і в раніше створених об’єктах, обидва методи найпростіше створити, описавши дві властивості з атрибутами «тільки для читання», призначення цих методів – дозволити користувачеві візуально контролювати, що відбувається з даними. Реалізація цих методів має вигляд:



function TProcess.Get_Order_List: Recordset;
begin
try
/ / Створюємо екземпляр контексту об’єкта Orders_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Orders_Data,
IOrders_Data, FOrders_Data));
/ / Створюємо набір даних
Result := CoRecordset.Create;
Result.CursorLocation := adUseClient;
/ / Отримуємо дані від об’єкта Orders_Data
Result := FOrders_Data.Get_Order_List;
/ / Інформуємо служби компонентів про успішне
/ / Виконанні запиту
SetComplete;
except
/ / Інформуємо служби компонентів про невдалу спробу
/ / Виконання запиту
SetAbort;
/ / Передаємо виняток зухвалому додатком
raise;
end;
end;

function TProcess.Get_Product_List: Recordset;
begin
try
/ / Створюємо екземпляр контексту об’єкта Stock_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Stock_Data,
IStock_Data, FStock_Data));
/ / Створюємо набір даних
Result := CoRecordset.Create;
Result.CursorLocation := adUseClient;
/ / Отримуємо дані від об’єкта Stock_Data
Result := FStock_Data.Get_ProductList;
/ / Інформуємо служби компонентів про успішне
/ / Виконанні запиту
SetComplete;
except
/ / Інформуємо служби компонентів про невдалу спробу
/ / Виконання запиту
SetAbort;
/ / Передаємо виняток зухвалому додатком
raise;
end;
end;


У наведеному вище фрагменті коду ми використовуємо метод CreateInstance контексту викликається об’єкта замість створення самого об’єкта. Це дозволяє нам використовувати об’єкти саме тоді, коли вони дійсно потрібні, і тим самим заощадити ресурси операційної системи. Щоб змусити ці методи працювати коректно, слід описати змінні, які містять посилання на викликаються об’єкти:



private
FOrders_Data: IOrders_Data ;
FStock_Data: IStock_Data ;


Крім цього слід послатися на бібліотеки типів раніше створених об’єктів. Найпростіше скопіювати файли PROC.TLB, PROC_TLB.PAS, STOCK.TLB і STOCK_TLB.PAS в каталог з поточним проектом і включити посилання на модулі PROC_TLB.PAS і STOCK_TLB.PAS в пропозицію uses, поряд з посиланнями на модулі ADODB, ADODB_TLB, ADOR_TLB.


Третій метод – Process_Order – буде реалізовувати транзакцію, яка одночасно модифікує обидві таблиці: Product і OrderedProduct (або скасовує всі зміни, якщо під час її роботи відбулося виняток). Параметри цього методу будуть наступними:



Реалізація методу ProcessOrder має такий вигляд:



procedure TProcess.Process_Order(Prod_ID: Integer;
const Prod_Name: WideString; UnitPrice: Currency;
Quantity: Integer; const Addr: WideString);
begin
try
/ / Створюємо контекст об’єкта Stock_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Stock_Data,
IStock_Data, FStock_Data));
/ / Створюємо контекст об’єкта Orders_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Orders_Data,
IOrders_Data, FOrders_Data));
/ / Додаємо запис до таблиці OrderedProducts
FOrders_Data.Add_Order(Addr,
Prod_Name,UnitPrice,Quantity);
/ / Зменшуємо значення поля UnitsInStock
FStock_Data.Dec_UnitsInStock(Prod_ID,Quantity);
/ / Збільшуємо значення UnitsOnOrder
FStock_Data.Inc_UnitsOnOrder(Prod_ID,Quantity);
/ / Повідомляємо службам компонентів,
/ / Що транзакція може бути завершена
EnableCommit;
except
/ / Повідомляємо службам компонентів,
/ / Що транзакція повинна бути скасована
DisableCommit;
/ / Передаємо клієнту виняток
raise;
end;
end;


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



FStock_Data.Dec_UnitsInStock(Prod_ID,Quantity);


Причина цього полягає в можливому порушенні обмеження на значення поля UnitsInStock таблиці Products: воно завжди повинно бути невід’ємним (це ж число одиниць товару на складі), а в методі з нього віднімається ціле число. При виникненні пов’язаного з цим виключення результат виконання попереднього методу повинен бути анульований, і трохи пізніше ми в цьому переконаємося.


Зберігши і скомпілювавши проект, скопіюємо створену бібліотеку proc.dll на серверний комп’ютер і зареєструємо в службах компонентів в тому ж додатку COMPlus_Demo, що й попередні два компонента (рис. 2).

Рис. 2. Додаток COM + для реалізації транзакцій


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


Тестування транзакцій


Створимо новий проект клієнтського додатку. На його головну форму помістимо компонент TRDSConnection і два компонента TADODataSet. Властивість ServerName компонента RDSConnection1 встановимо рівним програмного ідентифікатору (ProgID) третього зі створених нами компонентів (в даному випадку – proc.Process), а властивість ComputerName встановимо рівним імені серверного комп’ютера. Як і у випадку попереднього клієнтського додатки, ніяких властивостей компонентів TADODataSet встановлювати не потрібно, оскільки на етапі виконання ці компоненти отримають набори даних від об’єкта Process. Щоб користувач міг побачити їх вміст, помістимо на форму по два компонента TDataSource і TDBGrid і зв’яжемо їх з відповідним компонентом TADODataSet. Нарешті, помістимо на форму два компоненти TEdit для введення кількості одиниць замовленого товару і адреси клієнта, а також дві кнопки, які ініціюють виклики методів об’єкту Process.


Створимо оброблювачі події OnClick цих кнопок. Перший з них ініціює отримання і відображення двох наборів даних, наданих об’єктом Process, а саме результатів запитів до таблиць Products і OrderedProducts:



procedure TForm1.Button1Click(Sender: TObject);
begin
try
/ / Ініціюємо створення контексту об’єкта Process
RDSConnection1.Connected := True;
/ / Отримуємо список замовлень
RS := RDSConnection1.GetRecordset(Order_List,);
ADODataSet2.Recordset := RS;
ADODataSet2.Open;
/ / Отримуємо список товарів
RS := RDSConnection1.GetRecordset(Product_List,);
ADODataSet1.Recordset := RS;
ADODataSet1.Open;
/ / Можна приймати замовлення
Button2.Enabled := True;
except
/ / Деякі з об’єктів недоступні
ShowMessage (Дані не отримані);
Button2.Enabled := False;
end;
RDSConnection1.Connected:=False;
end;


Для того щоб наведений вище фрагмент коду був працездатний, необхідно послатися на модуль ADODB в пропозиції uses і додати опис змінної RS:



var
Form1 : TForm1;
RS :_Recordset;


Другий обробник події ініціює транзакцію, викликаючи метод ProcessOrder:
procedure TForm1.Button2Click(Sender: TObject);
begin
try
/ / Ініціюємо створення контексту об’єкта Process
RDSConnection1.Connected := True;
/ / Викликаємо його метод Process_Order
RDSConnection1.AppServer.Process_Order(
ADODataSet1.FieldByName(ProductID).AsInteger,
ADODataSet1.FieldByName(ProductName).AsString,
ADODataSet1.FieldByName(UnitPrice).AsFloat,
StrToInt(Edit1.Text),Edit2.Text);
/ / Оновлюємо дані
Button1Click(self);
except
/ / В одному з серверних об’єктів виникло виняток
ShowMessage (Замовлення не прийнятий);
Button2.Enabled := False;
end;
RDSConnection1.Connected := False;
end;


Скомпілюємо і збережемо проект, а потім скопіюємо отримане додаток на клієнтський комп’ютер.


Виконаємо клієнтську програму. Після натискання першої кнопки дані з таблиць Products і OrderedProducts будуть відображені в компонентах TDBGrid. Далі ми можемо вибрати рядок в наборі даних з таблиці Products, ввести адресу та кількість замовленого товару у відповідних компонентах TEdit і натиснути другу кнопку, що призведе до виконання транзакції. Якщо кількість замовлених одиниць товару не перевищує значення поля UnitsInStock в обраній записи, після оновлення даних ми отримаємо наступні результати:



Слід зазначити, що якщо при виконанні транзакції не відбудеться винятків, значення поля Ord_ID нового запису, яка з’явиться в полі Ord_Id таблиці OrderedProducts, буде дорівнює значенню того ж поля попереднього запису плюс 1 (рис. 3), однак при виникненні виключення ситуація буде іншою. Спробуємо ввести в поле Кількість число, що перевищує значення в поле UnitsInStock (тобто замовити товарів більше, ніж є на складі). В цьому випадку спочатку об’єкт Oders_Data додасть запис в таблицю OrderedProducts, а потім об’єкт Stock_Data почне виконувати свій метод Dec_UnitsInStock, і в ньому відбудеться виняток, пов’язане з порушенням наявного в базі даних обмеження на значення цього поля (так як на складі не повинно бути негативного числа одиниць товару). Внаслідок цього відбудеться відкат транзакції, і новостворена запис, природно, буде видалена з таблиці, але при цьому поточне значення поля Ord_Id збережеться (нагадаємо, що це поле типу Identity і його значення генеруються автоматично). Якщо потім за допомогою того ж додатка ми спробуємо обробити інше замовлення, що задовольняє обмеженням на значення поля UnitsInStock, ми виявимо, що в значеннях поля Ord_Id з’явилися пропуски – запис з попереднім значенням цього поля в таблиці OrderedProducts відсутня. Отже, відкат попередньої транзакції дійсно відбувся.

Рис. 3. Клієнтський додаток для тестування транзакцій


Звичайно, може виникнути резонне питання: навіщо потрібно було створювати ці три об’єкти, коли можна просто використовувати механізм підтримки транзакцій, наданий самим сервером баз даних? Дійсно, в разі однієї і тієї ж бази даних (або навіть різних баз даних, керованих однаковими серверами) це може і не має особливого значення. Однак при використанні серверів баз даних в цьому є прямий сенс: механізми підтримки транзакцій серверів баз даних звичайно не підтримують розподілені транзакції за участю СУБД інших виробників, а за допомогою служб компонентів їх все-таки можна здійснити. Наш наступний приклад продемонструє, як це зробити.


Управління розподіленими транзакціями


Для ілюстрації реалізації розподілених транзакцій за допомогою служб компонентів ми злегка модифікуємо наше додаток. Тепер ми будемо використовувати дві СУБД – Microsoft SQL Server 2000 і Oracle 8.0.4.


Відзначимо, що використання баз даних Oracle в розподілених транзакцій, заснованих на застосуванні об’єктів COM +, можливо починаючи з Oracle 7.3.3. Саме ця версія Oracle і є версією за замовчуванням, описаної в ключі реєстру:



HKEY_LOCAL_MACHINESOFTWAREMicrosoftMSDTCMTxOCI


За замовчуванням значення цього ключа такі:



OracleOciLib = “ociw32.dll”
OracleXaLib = “xa73.dll”
OracleSqlLib = “sqllib18.dll”


У разі застосування Oracle8 слід внести зміни в ці значення:



OracleOciLib = “ociw32.dll”
OracleXaLib = “xa80.dll”
OracleSqlLib = “sqllib80.dll”


Слід також мати на увазі, що розподілені транзакції за участю Oracle можливі при застосуванні ODBC-драйвера, що входить в комплект Microsoft Data Access Components, а не в комплект поставки Oracle 8.


У разі застосування Oracle8i, крім модифікації ключів реєстру, необхідно створити додатковий сервіс, призначений для підтримок розподілених транзакцій. Зацікавлені цим питанням можуть звернутися до розділу документації Oracle «Using Microsoft Transaction Server with Oracle8i».


Після цього короткого вступу розглянемо, як можна модифікувати наведений приклад для реалізації в ньому розподіленої транзакції (такі дії відповідають застосуванню Microsoft SQL Server 2000, Oracle 8.0.4, ODBC-драйверів зі складу Microsoft Data Access Components і OLE DB Provider for ODBC drivers).


Для початку скопіюємо таблицю Products з бази даних Northwind в базу даних Oracle (у нашому прикладі це стандартна база даних, яку можна згенерувати при установці Oracle). Зробити це можна, наприклад, за допомогою служб трансформації даних (Microsoft Data Transformation services). При перенесенні даних краще перейменувати таблицю Products в PRODUCTS, щоб не модифіковані тексти запитів в коді об’єкта Stock_Data.


Крім того, нам слід створити для зазначеної таблиці серверне обмеження, аналогічне обмеження, що була в базі даних Northwind. Для цього за допомогою утиліти SQL Plus потрібно виконати наступне SQL-пропозиція:



ALTER TABLE SCOTT.PRODUCTS
ADD CONSTRAINT PRODUCTSCHECKCONSTRAINT1
CHECK (UNITSINSTOCK>-1)


Далі слід описати ODBC-джерело для бази даних Oracle (за допомогою програми Data Sources розділу Administrative Tools панелі управління Windows). Назвемо його orcl_odbc.


Тепер потрібно змінити наш об’єкт Stock_Data. Всі модифікації полягають у зміні властивості ConnectionString компонента ADOConnection1:



Provider=MSDASQL.1;
Password=MANAGER;
Persist Security Info=True;
User ID=SYSTEM;
Data Source=orcl_odbc


І нарешті, можна помістити нову версію бібліотеки STOCK.DLL на серверний комп’ютер і запустити клієнтську програму. Ми можемо переконатися, що і в цьому випадку розподілені транзакції обробляються коректно. Зокрема, порушення серверного обмеження, описаного в Oracle, як і в попередньому випадку, призводить до відкоту транзакції в Microsoft SQL Server, що підтверджується появою «перепусток» в значеннях первинного ключа таблиці OrderedProducts.


Таким чином, за допомогою служб компонентів ми можемо організувати розподілену транзакцію, в якій беруть участь бази даних, керовані СУБД різних виробників.


Застосування подій COM +


Механізм повідомлення про події в службах компонентів


Говорячи про COM +, не можна не сказати про механізм обробки подій, що виникають в таких об’єктах. На відміну від подій COM, де за повідомленням стежить сам сервер, в COM + за подіями та повідомленням про них об’єктів-передплатників стежать служби компонентів. При цьому для генерації подій створюється окремий об’єкт – видавець (publisher), до якого проводиться звернення в момент настання події. Після цього служби компонентів звертаються до всіх клієнтів, підписаним на дану подію.


Об’єкти-видавці подій COM + не повинні містити реалізації своїх інтерфейсів – вона міститься не в серверному об’єкті, а в клієнтському додатку (в передплатника на дану подію). Тому після опису інтерфейсів компонент компілюється і реєструється в службах компонентів. При настанні події в об’єкті COM + слід ініціювати створення об’єкта, що описує події COM +, і викликати метод, відповідний даної події. В об’єкті-передплатника (це звичайний COM-об’єкт) зазвичай реалізується інтерфейс об’єкта-видавця та його методи – в даному випадку реалізація методів відіграє роль обробників цих подій. Зазначимо, що і об’єкт-видавець, і об’єкт-передплатник повинні бути зареєстровані в одному і тому ж додатку COM +.


Подією в об’єкті COM + може бути наступ будь-якого факту (наприклад, в розглянутому вище прикладі подіями можуть бути поява сотого замовлення, замовлення якогось конкретного товару, спроба замовлення товару в більшій кількості, ніж є на складі, і т.д.). В цьому випадку в коді об’єкта, що ініціює подія (в розглянутому вище прикладі – в коді об’єкта Process) повинен бути присутнім код, який реалізує створення екземпляра об’єкту подій і виклику методу, що відповідає цій події. Наприклад, якщо об’єкт подій називається MyEvent, інтерфейс об’єкта подій – IMyEvent, а метод, що повідомляє про події, – OrdMessage володіє одним рядковим параметром, то фрагмент коду, який ініціює створення події, пов’язаної зі спробою замовлення товару в кількості, що перевищує наявне на складі, може виглядати, наприклад, так:



type
FMyEvent: IMyEvent ;

procedure TProcess.Process_Order(Prod_ID: Integer;
const Prod_Name: WideString; UnitPrice: Currency;
Quantity: Integer; const Addr: WideString);
begin
try
/ / Створюємо контекст об’єкта Stock_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Stock_Data,
IStock_Data, FStock_Data));
/ / Створюємо контекст об’єкта Orders_Data
OleCheck(ObjectContext.CreateInstance(CLASS_Orders_Data,
IOrders_Data, FOrders_Data));
/ / Додаємо запис до таблиці OrderedProducts
FOrders_Data.Add_Order(Addr,
Prod_Name,UnitPrice,Quantity);
/ / Зменшуємо значення поля UnitsInStock
FStock_Data.Dec_UnitsInStock(Prod_ID,Quantity);
/ / Збільшуємо значення UnitsOnOrder
FStock_Data.Inc_UnitsOnOrder(Prod_ID,Quantity);
/ / Повідомляємо службам компонентів,
/ / Що транзакція може бути завершена
EnableCommit;
except
/ / Інформуємо служби компонентів про невдалу спробу
/ / Виконання транзакції і генеруємо подію, про яку
/ / COM + повідомить додаток, що відповідає за доставку
/ / Товару на склад
OleCheck(ObjectContext.CreateInstance(CLASS_MyEvent,
IMyEvent, FMyEvent));
FMyEvent.OrdMessage (Товар + Prod_Name +
потрібен у кількості + IntToStr (Quantity) + упаковок);
DisableCommit;
/ / Передаємо клієнту виняток
raise;
end;
end;


Зазначимо, що екземпляр об’єкта подій може бути створений і з клієнтського додатку.



Створення об’єкта-видавця


Для його створення потрібно встановити перший і другий пакети оновлень (update packs) Delphi 6 (ці пакети оновлень доступні зареєстрованим користувачам Delphi на Web-сайті компанії Borland Software Corporation), оскільки вихідна версія Delphi 6 містила ряд помилок, пов’язаних з генерацією бібліотек типів об’єктів подій і виправлених в другому пакеті оновлення.


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


Почнемо зі створення об’єкта-видавця. З цією метою створимо нову бібліотеку ActiveX, а в ній – об’єкт події за допомогою вибору значка COM + Event Object на вкладці ActiveX вікна репозитарія об’єктів (привласнимо події ім’я EVT, а самому об’єкту – EVT_events). В результаті буде згенеровано бібліотека типів, що містить інтерфейс IEVT, до якого можна додавати методи, які відповідають різним можливим подіям.


Методів у інтерфейсу об’єкта-видавця може бути декілька. Обов’язкова вимога до цих методів – вони повинні повертати значення HRESULT, а їх параметри не повинні мати модифікаторів. Приклад додавання такого методу зображений на рис. 4.

Рис. 4. Бібліотека типів об’єкта подій COM +


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


Далі об’єкт події слід зареєструвати в службах компонентів. Рекомендується робити це не засобами Delphi, а засобами Component Services Explorer. Для цього створимо в службах компонентів нове додаток COM + (назвемо його event_demo) і виберемо з контекстного меню розділу Components цього додатка опцію New / Component. Після цього буде запущений майстер COM Component Install Wizard, в якому потрібно натиснути на кнопку поряд з написом Install new event classes (рис. 5).

Рис. 5. Реєстрація об’єкта-видавця подій в службах компонентів


Потім слід вибрати файл EVT_event.dll, а потім ті інтерфейси або окремі події, на які можуть бути підписані майбутні передплатники (в даному випадку це єдина подія – Event1, рис. 6).

Рис. 6. Вибір публікованих подій


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


Створення об’єкта-передплатника


Об’єкт-передплатник являє собою звичайний внутріпроцессний COM-сервер, який реалізує інтерфейс об’єкта-видавця. Його створення ми почнемо з генерації нової бібліотеки ActiveX. Далі на сторінці ActiveX вікна репозитарія об’єктів виберемо значок COM Object і у вікні майстра COM Object Wizard вкажемо ім’я нового об’єкта (нехай він називається Subscrl). Оскільки нам слід реалізувати готовий інтерфейс, натиснемо на кнопку List, потім у вікні натиснемо на кнопку Add Library, виберемо створену раніше бібліотеку EVT_events.dll, а потім знайдемо і виберемо в списку доступних інтерфейсів інтерфейс IEVT.


Тепер нам залишилося реалізувати метод Event1 даного інтерфейсу. У вихідному інтерфейсі цей метод, як і всі методи об’єктів подій, оголошений віртуальним і абстрактним (це зроблено для того, щоб не дозволити створити його реалізацію в об’єкті подій). Тому перше, що слід зробити при створенні коду об’єкта-передплатника, – видалити ключові слова virtual і abstract з визначення цього методу. Тепер можна встановити курсор на рядок з визначенням зазначеного методу і вибрати з контекстного меню редактора коду пункт Complete class at cursor.


Реалізація методу в нашому прикладі буде досить проста: ми виведемо діагностичне повідомлення на екран серверного комп’ютера:



procedure TSubscr.Event1(const Event1_data: WideString);
begin
ShowMessage(Event1_data);
end;


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


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


Оскільки за повідомлення абонентів про подію відповідають служби компонентів, їх слід проінформувати про те, що даний передплатник в них потребує. Робиться це шляхом створення підписки (subscription). Створити передплату можна, вибравши з контекстного меню розділу Subscriptions компонент-передплатник (в даному випадку subdll.Subscr) пункт New / Subscription. Після цього буде запущений майстер COM Subscription Wizard, де ми виберемо програмний ідентифікатор об’єкта-видавця, про який слід повідомляти наш об’єкт-передплатник (рис. 7).

Рис. 7. Вибір об’єкта-видавця для підписки


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


Склад створеного і сконфигурированного нами програми COM + зображений на рис. 8.

Рис. 8. Склад програми COM + для тестування подій


Далі нам слід протестувати роботу створеного додатку.



Тестування повідомлень про події


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


Створимо новий проект і додамо на головну форму створюваного додатка компоненти TButton, TEdit і TRDSConnection. Значення властивості ServerName компонента TRDSConnection встановимо рівним імені об’єкта події – EVT_events.evt. Як властивості ComputerName вкажемо ім’я серверного комп’ютера (при збігу серверного та клієнтського комп’ютера можна залишити цю властивість порожнім).


Створимо обробник події, пов’язаної з клацанням по кнопці:



procedure TForm1.Button1Click(Sender: TObject);
begin
try
RDSConnection1.Connected:=True;
RDSConnection1.AppServer.Event1(Edit1.Text);
except
Showmessage (‘Подія не згенеровано’);
end;
RDSConnection1.Connected:=False;
end;


Тепер скомпілюємо проект і запустимо його на виконання. Якщо ввести в компонент Edit1 рядок і натиснути на кнопку, буде створений екземпляр об’єкта-видавця та вироблено звернення до його методу Event1, після чого служби компонентів створять примірник об’єкта-передплатника і викличуть його метод з таким же ім’ям і з тим самим значенням його параметра. В результаті цього додаток dllhost.exe, в адресному просторі якого виконується передплатник, виведе на екран серверного комп’ютера діалогове вікно з рядком, введеної в клієнтському додатку (рис. 9).

Рис. 9. Тестування повідомлень про події


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


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


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


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

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

Ваш отзыв

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

*

*