Два простых способа уведомления
Оформил: DeeCo
Автор: Алексей Еремеев
В своей работе мне частенько приходиться делать разного рода
клиент-серверные системы. И совсем не обязательно на уровне глобальных сетей.
Речь пойдет о внутренних подсистемах. Например, имеем компонент, который
эмулирует секундомер. Запустили его с параметром типа "а напомни мне, что будет
полночь" и забыли. Ну и конечно событие есть типа OnAlert. И обработчик его
честно будет вызван по достижении нужной нам полуночи. Но обработчик один, а
захотели узнать об этом событии сразу десять разных объектов. Не вешать же
десять будильников? Конечно, проще в одном обработчике перебрать методы
уведомления этих десяти объектов да и дело с концом. Но можно поступить хитрее -
заставить объект-будильник самому напоминать всем кто попросит его об этом. Вот
о способах такого уведомления и пойдет речь.
Как условие - объект "сервер" ничего не знает об объекте "клиенте". После
некоторого размышления и перебрав несколько вариантов я пришел к выводу, что
наиболее приемлимые для практики есть два способа. Первый подсмотрен в WinAPI а
второй - чисто Дельфи. Оба способа основаны на простой идее регистрации клиента
на сервере и оповещении сервером клиентов по внутреннему списку
зарегистрированных клиентов.
Способ 1. Оповещение через механизм сообщений Windows. в модуле
объекта-сервера в интерфейсной части определяется пользовательский номер
события:
const
WM_NOTIFY_MSG = WM_USER + 123;
в объекте-сервере реализуются две интерфейсные процедуры (вкупе с
объявленным в приватной секции и созданным в конструкторе TList, в деструкторе
не забудем его разрушить, естественно)
procedure RegisterHandle(HW: THandle);
var
i: integer;
begin
i := FWindList.IndexOf(pointer(HW));
if i < 0 then
FWinList.Add(pointer(HW));
end;
procedure UnregisterHandle(HW: THandle)
var
i: integer;
begin
i := FWindList.IndexOf(pointer(HW));
if i >= 0 then
FWinList.Delete(i);
end;
и создается функция оповещения в приватной секции:
procedure SendNotify(wParam, lParam: integer);
var
i: integer;
begin
i := 0;
while i < FWinList.Count do
begin
SendMessage(integer(FWinList.Items[i]), WM_NOTIFY_MSG, wParam, lParam);
Inc(i);
end;
end;
можно вместо SendMessage использовать PostMessage, будет асинхронное
сообщение, иногда это выгодней, например для исключения возможности бесконечной
рекурсии.
Объект-клиент должен иметь хэндл окна, который регистрируется на
объекте-сервере и обработчик событий этого окна, который будет вызыватся при
оповещении сервером списка клиентов (окон). У объекта-клиента можно поступить
двояко. Если объект-клиент уже имеет хэндл окна (например, форма) то пишется
обработчик фиксированного номера события:
procedure ServMsg(var Msg: TMessage); message WM_NOTIFY_MSG;
или если окна нет, то создается универсальный метод-обработчик и невидимое
окно при помощи функции AllocateHWND() (пример смотрите в исходниках VCL -
объект TTimer)
Прелесть этого метода состоит в том, что объект-клиент может быть вообще
в другом приложении, нежели объект-сервер, и такой трюк пройдет при
использовании DLL. Кроме того передавать можно не только пару цифр, но и блоки
данных (и даже строки) при помощи сообщения WM_COPYDATA. Но это уже другая
история, а мы пока пойдем дальше.
Способ 2. Оповещение через объект-посредник. В отдельном модуле
создаем объект-посредник, который имеет один метод типа SendEvent и одну ссылку
на обработчик события OnEvent. Я назвал такой объект TSynaps (да простят меня
нейрохирурги)
unit Synaps;
interface
uses
Windows, Messages, SysUtils, Classes;
type
TSynaps = class(TObject)
private
FOnEvent: TNotifyEvent;
public
procedure SendEvent;
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
end;
implementation
procedure SendEvent;
begin
if Assigned(FOnEvent) then
try
FOnEvent(Self);
except
end;
end;
end;
Причем методов и событий может быть много разных на любой вкус. С
очередями, асинхронными "прослойками", задержками и другими наворотами. Тут уж
кто на что горазд. Я лишь демонстрирую идею. Модуль с объектом-сервером и модуль
с объектом-клиентом имеют право знать о модуле Synaps. В объекте-сервере
реализуются уже знакомые нам три функции (чуть иначе): в интерфейсе объекта:
procedure RegisterSynaps(Syn: TSynaps);
var
i: integer;
begin
i := FSynapsList.IndexOf(pointer(Syn));
if i < 0 then
FSynapsList.Add(pointer(Syn));
end;
procedure UnregisterSynaps(Syn: TSynaps);
var
i: integer;
begin
i := FSynapsList.IndexOf(pointer(Syn));
if i >= 0 then
FSynapsList.Delete(i);
end;
и приватная функция:
procedure NotifySynapses;
var
i: integer;
begin
i := 0;
while i < FSynapsList.Count do
begin
TSynaps(FSynapsList.Items[i]).SendEvent;
Inc(i);
end;
end;
Объект-клиент создает в себе объект-синапс, назначает его событию OnEvent
свой внутренний обработчик и регистрирует этот синапс на объекте-сервере. Вуаля!
И получает оттуда уведомления. Кстати, в деструктор синапса можно встроить вызов
события OnDestroy, и тогда объект-сервер, при регистрации клиента, может
назначить ему обработчик и автоматически разрегистрировать его при уничтожении.
Но это уже навороты.
Такой подход позволяет строить обратные вызовы любой сложности. К тому-же
это чистый паскаль-код без привязки к операционке. (а вдруг Kylix :о)
Итог. Как вы могли заметить, оба способа базируются на двух базовых
идеях. Первое - это регистрация клиента на сервере, и второе - вызов сервером
некой функции внутри клиента. Разница только в механизмах. И выбирать тут можно
исходя из вкусов, предпочтений и неких требований, связанных с ресурсоемкостью,
переносимостью и т. п. На самом деле есть очень широко распространенный и
давно известный метод под названием CallBack-функция. Мы вызываем кого-то и
передаем как один из параметров адрес другой функции. И этот метод частенько
используется в WinAPI (смотрите, к примеру, справку по функции EnumFonts). Но!
Механизм прямого CallBack-а довольно некрасиво ложится на объектную модель
Дельфи, так что я не стал описывать его здесь. Тем более, что оба способа -
то-же самое, но красивше. И самое последнее - не забывайте разрегистрировать
клиента в конце работы и освобождать ресурсы в деструкторе. И да известят вас
ваши сервера только о хорошем!
|