Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций РСОИ.doc
Скачиваний:
20
Добавлен:
04.11.2018
Размер:
1.93 Mб
Скачать

2.11. Інтерфейси зі зворотним викликом

Замість того, щоб використовувати інтерфейси диспетчирування для повернення події клієнту, можна створити звичайний інтерфейс, і визначити в ньому методи зі зворотним викликом. Такий інтерфейс зі зворотним викликом оголошується в СОМ-сервері, а реалізується на стороні клієнта.

Розглянемо процес створення застосунків сервера і клієнта, у яких буде продемонстровано процес використання адаптованого інтерфейсу, що підтримує зворотнє викликання від сервера до клієнта, тобто виклик сервером на виконання методів, реалізованих у клієнтському застосунку.

2.11.1. Створення сервера

Загальна схема використання інтерфейсів диспетчирування для повернення події клієнту подана нижче.

  1. Для демонстрації використання інтерфейсів диспетчирування будемо використовувати зовнішній сервер з об'єктом автоматизації IntfCallback.

  2. До складу бібліотеки типів автоматично буде включено інтерфейс IIntfСallback та відповідній до нього диспетчерський інтерфейс IIntfСallbackDisp.

IIntfСallback = interface(IDispatch);

IIntfСallbackDisp = dispInterface;

  1. За допомогою редактора бібліотеки типів необхідно додати ще один інтерфейс автоматизації – інтерфейс зворотного виклику IIntfCallbackEvents, для нього також буде створено відповідний диспетчерський інтерфейс IIntfCallbackEventsDisp.

IIntfCallbackEvents = interface(IDispatch)

IIntfCallbackEventsDisp = disІnterface

  1. В інтерфейс прямого виклику необхідно додати методи для забезпечення підключення клієнта до сервера:

function Connect (Callback: IIntfCallbackEvents):integer;

Клієнт буде викликати цей метод для встановлення зв'язку з CОМ-об'єктом. Як параметр клієнт повинен передати цьому методу вказівник на свій інтерфейс методів зворотного виклику. Метод є функцією і повертає клієнту ціле число, що інтерпретується клієнтом як його номер – ідентифікатор унікальний серед усіх клієнтів, яких обслуговує сервер.

function Disconnect(IdClient: Integer): boolean;

Викликається клієнтом в разі відключення від сервера

procedure SendText(text:WideString);

Клієнт викликає цей метод для передавання рядка тексту сервера.

5. За допомогою редактора бібліотеки типів необхідно сформувати склад інтерфейсу зворотного виклику, тобто тих методів, які в цьому інтерфейсі тільки оголошені, але не реалізовані. Їхні імена починаються з On, наприклад OnТext(Text: WideString).

При необхідності сервер повинен викликати ці методи, сформувавши параметри, тобто здійснити зворотнє викликання.

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

Розглянемо докладніше розробленням сервера. Для створення застосунка сервера скористаймося традиційною методикою — треба почати в середовищі Delphi новий проект і додати до нього об'єкт автоматизації. Виберіть у меню Delphi команду FileNew, а потім, коли відкриється діалогове вікно New Items, виберіть у ньому вкладку ActiveX. Після цього двічі клацніть на елементі Automation Objects на вкладці ActiveX. Delphi виведе на екран діалогове вікно майстра Automation Object Wizard. Заповніть поля у цьому вікні. Назвемо клас IntfCallback і для нього буде сформовано базовий інтерфейс IIntfCallback.

Після цього на екран автоматично буде виведене вікно Type Library Editor. Перше, що потрібно буде в ньому зробити, це створити інтерфейс, що буде використовуватися для зворотного викликання в застосунку-клієнті. Використовуючи панель інструментів і інші засоби Type Library Editor, додайте новий інтерфейс з ім’ям IIntfCallbackEvents. Додайте в цей інтерфейс єдиний метод OnText. Оголошення цього методу наведене нижче:

procedure OnText (text: WideString); safecall;

Тепер додайте в інтерфейс IIntfCallback три методи — Connect, Disconnect і SendText. Оголошення цих методів подані нижче:

function Connect(const Callback: IIntfCallbackEvents):Integer;

function Disconnect (UserID: Integer):Boolean;

procedure SendText (Text: WideString);

Клацніть на кнопці Refresh Implementation (Обновити реалізацію) панелі інструментів і закрийте редактор Type Library Editor.

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

Інтерфейс IIntfCallbackEvents буде реалізований у клієнтському застосунку, тому зараз не будемо зупинятися на реалізації його методу OnText. Але методи Connect, Disconnect і SendText ми зараз розглянемо докладніше.

Ініціалізація сервера

Коли сервер запитується клієнтом, викликається метод Initialize класу сервера. Програмний код методу TIntfCallback.Initialize наведено нижче:

procedure TIntfCallback.Initialize;

begin

inherited Initialize;

//Додати одиницю до глобального лічильника підключень.

Inc(NumConnections);

// Обновити кількість підключень в екранній формі

if NumConnections = 1 then

Form1.label2.Caption := '(1 active connections)';

else

Form1.label2.Caption := '('+IntToStr(NumConnections)+' active connections)';

end;

Спочатку викликається метод Initialize базового класу. При цьому збільшується на одиницю кількість підключень до сервера. Змінна NumConnections є глобальною. Вона має бути глобальною, оскільки метод викликається при підключенні клієнта до сервера. Нам треба централізовано стежити за поточною кількістю клієнтів, підключених до цього сервера.

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

Обробка підключень клієнтів

Клієнтські застосунки будуть викликати метод Connect класу сервера для встановлення зв'язку з ним.

Примітка 2. У назві методу Connect (Підключення) немає нічого магічного. З таким самим успіхом його можна назвати, наприклад, RegisterClient (Реєстрація клієнта).

Зазвичай бажання підключитися до сервера "висловлює" багато клієнтів. Тому метод Connect додає нового клієнта у внутрішній список клієнтів. Для цього створюється два допоміжних класи — TConn і TConns. Реалізація класів представлена в лістингу застосунка нижче за текстом.

Клас TConn відображає єдине підключення. Кожен клієнт повинен забезпечити реалізацію інтерфейсу IIntfCallbackEvents для сервера, що буде використовуватися сервером для викликання клієнта. Крім того, кожен клієнт має власний унікальний ідентифікатор, що використовується для його розпізнавання.

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

Зазначимо, що клас TConns має три методи, також названі Connect, Disconnect і SendText. Клас TIntfCallback, що реалізує інтерфейс IIntfCallback, просто передає керування однойменним методам класу TConns.

Виклик клієнта із сервера

Сервер звертається до клієнта шляхом викликання методу OnText інтерфейса IIntfCallbackEvents.

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

procedure TConns.SendText(Text: WideString);

var

Index: Integer;

C: TConn;

begin

for Index := 0 to FConns.Count - 1 do

begin

С:=TConn(FConns[index]);

C.FCallBack.OnText(Text);

end;

end;

У цій нескладній процедурі викликається метод OnText інтерфейсу IIntfCallbackEvents для всіх підключених клієнтів.

Вище ми зупинялися тільки на найбільш важливих фрагментах програмного коду сервера. Наведемо повний лістинг програмного коду сервера IntfSrv (файл IntfUnit.pas).

unit IntfUnit;

interface

uses

Windows, Classes, SysUtils, ComObj, ActiveX, IntfSrv_TLB;

type

// Клас, відповідальний за єдине підключення

TConn = class

public

FUserID: Integer;

FCallback: IIntfCallbackEvents;

destructor Destroy; override;

end;

// Клас, відповідальний за список поточних підключень

TConns = class

private

FConns: TList;

Сount: Integer;

public

constructor Create;

destructor Destroy; override;

function Connect(const Callback:IIntfCallbackEvents):Integer;

function Disconnect(UserID: Integer): Boolean;

procedure SendText(Text: WideString);

end;

// СОМ-об'єкт, до якого звертається клієнт

TIntfCallback = class(TAutoObject, IIntfCallback)

protected

function Connect(const Callback:IIntfCallbackEvents):Integer; safecall;

function Disconnect(UserID: Integer): WordBool; safecall;

procedure SendText(const Text: WideString); safecall;

public

procedure Initialize; override;

destructor Destroy; override;

function Connections: TConn;

end;

const

NumConnections: Integer =0;

var

GlobalConnections: TConns;

FLastUserID:Integer;

implementation

uses ComServ, MainForm;

{ TIntfCallback }

procedure TIntfCallback.SendText( const Text: WideString)

begin

// Передати текст через список підключень

Connections.SendText(Text);

end;

function TIntfCallback.Connect(const Callback: IIntfCallbackEvents):Integer;

//Додати нове підключення

begin

Result := Connections.Connect(Callback);

end;

destructor TIntfCallback.Destroy;

begin

// Зменшити на 1 кількість підключень,

Dec(NumConnections};

// Якщо не залишилося підключень, можна позбутися списку

if NumConnections = 0 then

begin

GlobalConnections.Free;

GlobalConnections:= nil;

end;

// Оновити кількість підключень в екранній формі

if NumConnections = 1 then

Form1.Label2.Caption := '(1 active connection)'

else

Form1.Label2.Caption:= '('+IntToStr(NumConnections)+'active connections)';

inherited Destroy;

end;

// Тут перевантажується метод Initialize класу TAutoObject.

// Метод можна використовувати замість конструктора

procedure TIntfCallback.Initialize;

begin

inherited Initialize;

// Додати одиницю до глобального лічильника підключень

Inc(NumConnections);

// оновити кількість підключень в екранній формі

if NumCоnnections = 1 then

Form1.Labe2.Caption := '(1 active connection)'

else

Form1.Label2.Caption:='('+IntToStr(NumConnections)+'active connections)';

end;

function TIntfCallback.Disconnect(UserID: Integer); WordBool;

begin

//Оброблення запиту на розрив підключення, що надійшов від //клієнта

Result := Connections.Disconnect(UserID);

end;

function TIntfCallback.Connections: TConns;

begin

// функція Connections повертає глобальний список підключень

if GlobalConnections = nil

then GlobalConnections := TConns.Create;

Result := GlobalConnections;

end;

{ TConn }

destructor TConn.Destroy;

begin

// Явно видаляється інтерфейс події

FCallback := nil;

inherited Destroy;

end;

{ TConns }

function TConns.Connect(const Callback: IIntfCallbackEvents): Integer;

var

C: TConn;

begin

// Присвоїти кожному підключенню унікальний ідентифікатор

Inc(Count);

// Сформувати нове підключення

С:= TConn.Create;

// Запам'ятати інтерфейс події

С.FCallback:= Callback;

// Встановити ID користувача

C.FUserID:= Count;

// Додати користувача в список

FConns.Add(C);

// Повернути ID, присвоєний користувачеві

Result:= Count;

end;

constructor TConns.Create;

begin

Count := 0;

FConns := TList.Create;

end;

destructor TConns.Destroy;

var

Index: Integer;

C: TConn;

Begin

for Index := 0 to Fconns.Count - 1 do

begin

C := TConn(FConns[Index]);

C.Free;

end;

FConns.Free;

end;

function TConns.Disconnect(UserID: Integer): Boolean;

var

Index: Integer;

C: TConn;

begin

Result := False;

for index := 0 to FConns.Count - 1 do

begin

C := TConn(FConns[Index]);

if C.FUserID = UserID then

begin

C.Free;

FConns.Delete(Index);

Result := True;

exit;

end;

end;

FConns.Count:= FConns.Count-1;

end;

procedure TConns.SendText(Text: WideString);

var

Index: Integer; C: TConn;

Begin

for Index := 0 to FConns.Count - 1 do

begin

C := TConn(FConns[Index]);

C.FCallback.OnText(Text);

end;

end;

initialization

// замініть параметр на ciSingleInstance, і тоді для кожного

// клієнта буде запускатися своя копія сервера

TAutoObjectFactory.Create(ComServer, TIntfCallback, Clas_IntfCallback, ciMultilnstance, tmApartment);

end.

Наведемо лістинг програмного коду модуля головної екранної форми застосунка IntfSrv.

unit MainForm;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, IntfUnit;

type

TForm1 = class(TForm)

Label1: TLabel;

Label2: TLabel;

private

{ Оголошення закритих членів}

public

{ Оголошення загальнодоступних членів }

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

end.

Наведемо скорочений (без коментарів) лістинг програмного коду бібліотеки типів сервера, сформований Delphi.

c А бібліотеці типів // • •

unit IntfSrv_TLB;

interface

uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;

const

LIBID IntfSrv:TGUID ='{E9D7678H-F7E3-11D2-909B-0040F6741DE2}'; IID_IIntfCallback:TGUID='{E9D7678F-F7E3-11D2-909B-0040F6741DE2}';

CLASS IntfCallback:TGUID='{E9D76791-F7E3-11D2-909B-0040F6741DE2}';

IID_IIntfCallbackEvents:TGUID='{E9E76793-F7E3-11D2-909B-G040F6741DE2}';

type

IntfCallback = interface

IIntfCallbackDisp = dispinterface;

IIntfCallbackEvents =interface;

IIntfCallbackEventsDisp =dispinterface;

// Оголошення сполучених класів, визначених у бібліотеці типів //IntfCallback = IntfCallback;

// Interface: IntfCallback

// Flags: (4416) Dual OleAutomation Dispatchable

IntfCallback = interface(IDispatch)

['{E9D7678F-F7E3-11D2-909B-0040F6741DE2}']

procedure SendText{const Text: WideString); safecall;

function Connect(const Callback:IIinfCallbackEvents): Integer; safecall;

function Disconnect(UserID: integer): WordBool; safecall;

end;

//DispIntf: IIntfCallbackDisp

IIntfCallbackDisp = dispinterface

['{E9D7678F-F7E3-11D2-909B-0040F6741DE2}']

procedure SendText(const Text: WideString); dispid 1;

function Connect(const Callback; IIntfCallbackEvents): Integer; dispid 2;

function Disconnect(UserID: Integer); WordBool; dispid 3; end;

// Interface: IIntfCallbackEvents

// Flags: (4416) Dual OleAutomation Dispatchable

IIntfCallbackEvents = interface(IDispatch)

['{E9D76793-F7E3-11D2-909B-0040F6741DE2}']

procedure OnText(const Text: WideString); safecall;

end;

// DispIntf: IIntfCallbackEventsDisp

// Flags; (4416) Dual OleAutomation Dispatchable

IIntfCallbackEvents = interface(IDispatch)

['{E9D76793-F7E3-11D2-909B-0040F6741DE2}']

procedure OnText(const Text: WideString); safecall;

end;

IIntfCallbackEventDisp=dispinterface

['{E9D76793-F7E3-11D2-909B-0040F6741DE2}']

procedure OnText(const Text: WideString); dispid 1;

end;

CoIntfCallback = class

class function Create: IIntfCallback;

class function CreateRemote(const MachineName: string): IIntfCallback;

end;

implementation

uses ComObj;

class function CoIntfCallback.Create: IIntfCallback;

begin

Result:=CreateComObject(CLASS_IntfCallback) as IIntfCallback;

end;

class function CoIntfCallback.CreateRemote(const MachineName: string): IIntfCallback;

begin

Result:=CreateRemoteComObject(MachineName, CLASS_IntfCallback) as IIntfCallback;

end;

end.