скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Два простых способа уведомления



Автор: Алексей Еремеев

В своей работе мне частенько приходиться делать разного рода клиент-серверные системы.
И совсем не обязательно на уровне глобальных сетей. Речь пойдет о внутренних подсистемах.
Например, имеем компонент, который эмулирует секундомер. Запустили его с параметром типа "а напомни мне, что будет полночь" и забыли. Ну и конечно событие есть типа 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-а довольно некрасиво ложится на объектную модель Дельфи, так что я не стал описывать его здесь. Тем более, что оба способа - то-же самое, но красивше. И самое последнее - не забывайте разрегистрировать клиента в конце работы и освобождать ресурсы в деструкторе. И да известят вас ваши сервера только о хорошем!





Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook