Продолжается подписка на наши издания! Вы не забыли подписаться?

Система поддержки событий COM+

Введение

Распределенные приложения, в которых сервер должен оповещать клиентов об изменениях важной для них информации, встречаются довольно часто. Такими программами могут быть приложения, отслеживающие стоимость акций и сообщающие об изменениях их курса, программы мониторинга погоды, отслеживающие показания удаленных датчиков атмосферного давления и оповещающие о его изменении, медицинские программы, выдающие на монитор дежурной сестры изменения жизненно важных показателей здоровья пациента. Это может быть и подчиненный сервер, предназначенный для параллельного выполнения некой ресурсоемкой задачи. А может быть просто сетевая программа, в которой надо обеспечить большую интерактивность. Вообще, оповещение заинтересованных сторон об изменениях в данных является классической проблемой распределенных вычислений.

В предыдущем номере нашего журнала, в статье “QC-компоненты Windows 2000 vs. MSMQ”, мы описывали Queued-компоненты на примере распределенного приложения, рассылающего удаленным клиентам сообщения от программы-передатчика.

В таком приложении трудно понять, кто является сервером, а кто – клиентом. Клиенты в момент получения уведомлений сами становятся серверами. Чтобы избежать путаницы, следуя примеру Microsoft, назовем программу, рассылающую сообщения «Издателем» (Publisher), а приложения, заинтересованные в получении уведомлений – «Подписчиками» (Subscriber).

В прошлый раз мы разрабатывали и испытывали приложение, обеспечивающее рассылку текстовых сообщений на компьютеры клиентов. Напомним о порядке его работы, но в новых терминах. Издатель, смотри Рис. 1, визуальное приложение Publisher.exe, инициирует рассылку текстовых сообщений через событийный объект – TestEventClass (локально или с удаленного компьютера). Сообщения рассылаются зарегистрированным подписчикам, которые представлены объектами TestEventSubscriber. Далее, сообщения рассылаются через объект Distributor приложениям-визуализаторам (SubscriberMonitor.exe), которые подключаются к объекту Distributor в момент своей загрузки.

Рис. 1 Взаимодействие компонентов распределенного приложения.

На первый взгляд все довольно просто. Но создание таких распределенных приложений может вызвать некоторые затруднения.

Первое заключается в том, что разработчик должен сам реализовать механизм регистрации и отключения удаленных подписчиков, получающих сообщения. В прошлый раз мы упростили реализацию этого механизма, задавая список зарегистрированных подписчиков статически, ведь наша цель заключалась в оценке Queued-компонентов, а не в создании действующего приложения. В реальной жизни, при необходимости создания подобного приложения, разработчику пришлось бы реализовывать не только обработку подключения и отключения клиентского приложения к серверу, но и учитывать потенциально возможные ошибки, например, некорректные отключения компьютеров клиентов. Разработка такого механизма не сложна, но довольно трудоемка. В предлагаемой статье мы опишем реализованную в Windows 2000 новую инфраструктуру – систему поддержки событий (COM+ Event System). Она призвана помочь разработчикам распределенных приложений, реализующим механизм рассылки уведомлений. К тому же, на реальных примерах можно будет увидеть, насколько проще и понятнее становится код подобного приложения.

Вторая сложность заключается не столько в реализации, сколько в разнообразии, большом количестве и, следовательно, слабой читаемости представленного кода в статье предыдущего номера. Для создания приложений, имеющих графический интерфейс, мы использовали Visual Basic (VB), а для не имеющих - Visual C++ (VC) и Active Template Library (ATL). Если вспомнить, что мы опробовали помимо Queued-режима рассылки, DCOM и MSMQ, то за горой C++ кода можно потерять суть обсуждаемой проблемы. В нынешней реализации мы постарались обойтись только кодом на VB. Это у нас не совсем получилось (причина будет объяснена далее), хотя основные возможности системы поддержки событий разработчику доступны из VB. В этой среде можно создавать мощные и полнофункциональные распределенные приложения, причем относительно быстро и легко. Скорость, главным образом, достигается благодаря сокращению кода, укороченным (по сравнению с C++) конструкциям работы с COM-объектами и за счет сокращения количества ошибок (это тоже заслуга более высокоуровнего языка). Если вы предпочитаете C/C++ или Pascal, то без труда сможете переложить код с VB на ваш любимый язык (обратное было бы уже не так очевидно).

Издатели и подписчики, существующий подход

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

Простейший подход – постоянно теребить издателя, аналогично тому, как заядлый биржевой игрок постоянно звонит своему брокеру, чтобы узнать последний курс акций. В терминах COM, издатель должен снабдить подписчика интерфейсом, а подписчик должен время от времени вызывать метод интерфейса, чтобы узнать, не случилось ли чего, как показано на Рис. 2.


Рис. 2
. Опрос издателя

Такой механизм легко запрограммировать, но идея ужасна в силу того, что на сетевые вызовы от клиентов, вроде «Не случилось ли чего?» уходит неимоверное количество времени и сил. Это заставляет издателя судорожно отвечать «Нет не случилось!». Работы много, толку … Каждый, кто когда-либо скрежетал зубами в ответ на десятый за последние столько же минут вопрос своего чада: "Папа, мы уже приехали?", поймет всю фундаментальную неправильность такого подхода.

А нужно лишь, чтобы издатель запускал процесс оповещения подписчиков, если заметил, что произошли интересующие подписчиков события. Вместо периодических звонков брокеру насчет последнего курса акций, можно дать брокеру номер своего телефона, попросив позвонить в случае каких-либо изменений. В терминах COM, подписчик предоставляет издателю интерфейс, а издатель вызывает его метод в случае каких-то интересных событий. Это подход используется ActiveX-компонентами для посылки сообщений своим контейнерам (см. Рис. 3). Здесь компонент служит издателем, а контейнер – подписчиком (В реальной жизни все несколько сложнее, но для объяснения принципов некоторое упрощение не помешает.).

Рис. 3. Обратные вызовы в ActiveX’ах

Такая идеология называется тесно связанными событиями (Tightly coupled Events, TCE). Подписчик точно знает, от кого требовать уведомлений (контейнер знает CLSID компонента) и механизм подключения к нему (интерфейс IConnectionPointContainer и IConnectionPoint, выставляемые компонентом). Тесно связанные события работают относительно неплохо на одном компьютере, но использовать его в масштабах корпоративной системы очень неудобно по следующим причинам:

Одним из решений этих проблем может быть внешнее хранение информации о подписке. При этом издатель должен будет поддерживать внешнюю БД, содержащую список рассылаемых событий. Подписчики могли бы читать этот список и выбирать нужные им события. Издатель должен поддерживать и некую базу данных о подписках, концептуально сходную с листом рассылки (или иметь другой способ найти необходимого подписчика). В случае с COM+, это список CLSID подписчиков, желающих знать о наступлении некоторого события. Доступные подписчикам административные средства должны позволять производить такую запись в эту БД. Когда издатель хочет разослать сообщение о событии, он обращается к этой БД, находит CLSID всех заинтересованных подписчиков, создает новый объект для каждого заинтересованного класса и вызывает метод этого объекта. Такую архитектуру можно назвать слабосвязанной, поскольку информация о том кто, что и от кого хочет услышать, будет храниться в центральной БД. В архитектуре тесно связанных событий эта информация непосредственно внедрена в программу.

Конечно, если такой механизм разрабатывать самостоятельно, потребуется большое количество сил и времени. Разработать код создания и поддержки базы данных событий и подписок, написать весь код для механизма оповещения и административных средств – задача не из легких. Необходимо сделать немало продаж, прежде, чем эта работа окупится. Объем продаж корпоративных приложений весьма невелик, если сравнить его с объемом продаж настольных приложений или операционных систем. Так, что лучше оставить эту задачу производителю ОС или средств разработки, а сэкономленные время и деньги лучше потратить на бизнес-логику, ведь покупатели платят не за инфраструктуру, а за конечное решение. В Windows 2000 инфраструктуру поддержки событий обеспечивает COM+ Event System (Система поддержки событий COM+).

Система поддержки событий COM+, 
идеология и архитектура

Введем новые термины. Если вам скучно, можете пропустить следующий абзац, а в случае трудностей с терминологией, возвратитесь и прочтите его.

Система поддержки событий COM+ - это служба операционной системы, предоставляющая базовые сервисы поддержки событийного механизма. Событие в этой системе описывается как метод некоторого COM-интерфейса. Интерфейс необходимо поместить в «COM+»-объект, который в свою очередь специальным образом зарегистрировать в системе поддержки событий. Не правда ли, очень похоже на сказку про Василису прекрасную – «Смерть кощеева на конце иглы, игла в яйце, яйцо в утке, утка в зайце...». Рассылка событий – процесс, инициируемый издателем и осуществляющий уведомление подписчиков о случившемся событии. Издатель – любая программа, инициализирующая событие посредством вызова метода интерфейса, принадлежащего событийному объекту. Подписчик – «COM+»-объект, реализующий тот же самый событийный интерфейс, что и событийный класс. Подписчик должен быть зарегистрирован как обычный «COM+»-компонент. Для того чтобы получать уведомления о событиях необходимо, чтобы для этого подписчика была оформлена (создана) подписка. Подписка – формальная регистрация производящаяся на компьютере где установлен класс событий. Сама подписка производится в ветке subscriptions на зарегистрированном здесь же подписчике. Класс событий (или Событийный класс) – «COM+»-объект, содержащий событийный интерфейс. Этот объект должен быть специальным образом зарегистрирован в системе поддержки событий. Ни одного экземпляра этого класса никогда не создается. При попытке создания экземпляра этого класса система поддержки событий создает специальный событийный объект, указатель на который и передается Издателю. Событийный объект – квази-компонент создаваемый на базе описания событийного интерфейса, взятого из событийного класса. Событийный интерфейс – интерфейс, который должен реализовать издатель и все подписчики, которые хотят получать уведомления о событиях. Вызов методов этого событийного интерфейса и инициирует рассылку сообщений. Замечу, что подписка может быть осуществлена на конкретный метод этого интерфейса, но реализовать интерфейс придется полностью. Это не проблема, так как VB скрывает сложности реализации интерфейсов, а в VC есть визард, автоматизирующий эту операцию.

Подписчик реализует интерфейс как COM-сервер; издатель делает вызовы как COM-клиент. Единственным отличием от классического COM является выступающая посредником между издателем и подписчиком система поддержки событий, которая отслеживает подписчиков, желающих получать уведомления и направляет им эти вызовы. При этом система поддержки событий не нуждается в каких-либо специфических знаниях об издателе.

Схема взаимодействия частей системы поддержки событий показаны на Рис. 4.

Рис. 4 Схема взаимодействия частей системы поддержки событий

Издатель создает событийный объект вызовом функции CreateObject (CoCreateInstance – для C++ программистов). Созданный системой поддержки событий объект содержит реализацию запрашиваемого интерфейса.

Через вызов необходимого метода событийного интерфейса издатель инициирует рассылку уведомлений о событии (на языке американских программистов - производит Firing).

Система поддержки событий просматривает базу данных (COM+ каталог) в поисках зарегистрированных подписчиков (на соответствующий интерфейс или метод). Она связывается с подписчиками (создавая их напрямую, через моникер или Queued-компонент, как указано в базе данных подписки («COM+»-каталоге)) и уведомляет их о событиях, вызывая у них соответствующий метод событийного интерфейса.

Если еще раз взглянуть на Рис. 1, то можно увидеть, что механизм доставки текстовых сообщений от издателя (Publisher) к подписчикам (TestSubscriber) в нашем распределенном приложении удобно реализовать с помощью Системы поддержки событий (COM+ Event System). Уведомления же визуальным приложениям на компьютере подписчика удобно реализовать в объекте Distributor стандартным для VB способом. Таким образом, в нашем приложении будут реализованы оба механизма:

Регистрируя класс событий, мы сообщаем системе поддержки событий, какие интерфейсы и методы содержит класс событий. Описание класса событий хранятся в COM+ каталоге. Произвести регистрацию можно либо с помощью средств администрирования COM+, либо программным способом.

В системе поддержки событий подписку производит наделенное администраторскими правами лицо (через Component Services консоль) или программа. Подписка - это структура данных, снабжающая сервис событий информацией о подписчике. Она определяет, кто и от кого будет получать уведомления. Естественно, в терминах COM «кто и от кого» определяются через CLSID соответствующих объектов. Кроме CLSID подписчика можно вдобавок указывать и имя компьютера, на котором необходимо создавать объект-подписчик (вы еще не забыли, подписчик тоже является «COM+»-объектом).

Виды подписок

Подписки могут быть постоянными – Persistent (собственно о них и шла речь во всех предыдущих рассуждениях), временными – Transient и «PerUser» (для конкретного пользователя). Постоянная подписка хранится в COM+ каталоге до тех пор, пока ее оттуда не удалят. Подписка, созданная с помощью Component Services консоли является постоянной.

Временные подписки сохраняются лишь до перезагрузки системы. Их можно создать в скрытом от средств администрирования каталоге "TransientSubscriptions". Такие подписки создаются только программным способом, с использованием компонентов административной системы COM+. После создания временной подписки необходимо установить IID событийного интерфейса в ее свойстве InterfaceID. Преимущество этой подписки в том, что системе поддержки событий не придется каждый раз создавать объект-подписчик, и удалять его после исполнения событийного метода - это позволяет увеличить производительность.

PerUser-подписка является разновидностью постоянной подписки и позволяют получать уведомления о событиях только после входа определенного пользователя в систему. После этого система событий включает все подписки, для которых флаг PerUser установлен в TRUE, а «OwnerSID» соответствует имени вошедшего в систему пользователя. Этот вид подписки работает только когда и издатель и подписчик расположены на одном компьютере. После отключения пользователя PerUser-подписка становится недоступной. PerUser-подписку нельзя создать с помощью средств администрирования (только программно).

Инициирование рассылки событий

Чтобы инициировать рассылку событий, издатель создает событийный объект нужного класса событий. Для этого используются стандартные методы создания объектов: оператор New, CreateObject или GetObject в VB и CoCreateInstance или CoGetObject в C/C++. Созданный объект реализует требуемый событийный интерфейс. Далее издатель вызывает необходимый метод. Созданная системой событий реализация интерфейса просматривает COM+ каталог и находит всех подписчиков (имеющих зарегистрированную подписку на этот интерфейс или его метод). После этого система событий поочередно или параллельно (в зависимости от настроек событийного класса) создает экземпляр каждого зарегистрированного подписчика и вызывает событийный метод.

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

COM+ не дает возможность определить порядок вызова нескольких подписчиков. По умолчанию, подписчики оповещаются в неопределенном порядке. Система не гарантирует повторения одного и того же порядка оповещения подписчиков. Издатели, которые хотят управлять порядком рассылки, должны использовать специальные объекты – фильтры событий.

Фильтрация событий

Фильтрация служит для управления процессом рассылки уведомлений. Например, медицинское приложение может сигнализировать о повышении артериального давления у пациента, если оно превысит некий предел, а не каждый раз, когда оно изменится.

Фильтрация решает следующие проблемы. Во-первых, она позволяет более тонко настраивать механизм рассылки. Во-вторых, стандартизирует механизм управления рассылкой. В-третьих, повышает производительность приложений, так как работает на компьютере, где зарегистрирован событийный класс, предотвращая лишние вызовы по сети. Существует два способа фильтрации: фильтрация по критерию и издательская фильтрация.

Фильтрация по критерию основывается на вычислении выражения, записанного в свойстве подписки «Filter Criteria». Записать выражение можно с помощью консоли Component Services. Имена параметров, используемые в критерии, сверяются системой с описанием из библиотеки типов. В момент вызова значения параметров подставляются в выражение. Производится расчет этого выражения, и, если выражение возвращает TRUE, то метод будет вызван у подписчика. В противном случае - нет. Позже будет показано, как можно задать критерий фильтрации.

Фильтрация по критерию решает задачу – кому отсылать уведомление, а кому нет. Порядок вызова подписчиков она не определяет. А для некоторых приложений это важно. Например, если подписчик внес дополнительную плату за срочность. Объектные издательские фильтры могут решить эту проблему. Издательский фильтр – это компонент, реализующий интерфейс IMultiPublisherFilter. Он подключается к событийному объекту путем вызова метода IEventControl.SetPublisherFilter(). Сам интерфейс IEventControl можно запросить у событийного объекта. Вообще, это не очень хороший способ, так как он не может работать с Queued-компонентами. Лучший способ – задать CLSID издательского фильтра в свойстве MultiPublisherFilterCLSID событийного класса. При создании событийного объекта, система, основываясь на этом свойстве, будет создавать экземпляр издательского фильтра в момент рассылки уведомлений. Далее мы покажем на примере, как можно реализовать издательскую фильтрацию.

Система поддержки событий и Queued-компоненты

События и Queued-компоненты решают разные проблемы. События существуют для связи издателя с подписчиками, при этом механизм событий COM+ административно отслеживает желаемые пути доставки, что экономит массу времени разработчика. Queued-компоненты, с другой стороны, осуществляют асинхронную передачу COM-вызовов. Queued-механизм освобождает разработчика от контроля за сроками жизни клиента и сервера, что опять же экономит массу времени разработчика. Мало пользы в событиях, требующих одновременной работы всех заинтересованных в доставке сторон.

Требование совпадения жизненных циклов может стать большой проблемой в корпоративном приложении, как было описано в статье предыдущего номера. Технология Queued-компонентов тесно вплетена в систему поддержки событий. Queued-компоненты могут использоваться как для обеспечения независимости приложения издателя и событийного объекта, так и для обеспечения независимости времени исполнения событийного объекта и приложений подписчиков, как показано на Рис. 5.

Рис. 5.  Взаимодействие Queued-технологии и системы поддержки событий

Какие подводные камни скрыты в использовании Queued-компонентов с системой событий? Главное – порядок доставки событий. Queued-компоненты записывают и воспроизводят все вызовы, сделанные за срок жизни конкретного объекта, в одно MSMQ-сообщение. Все вызовы одного Queued-объекта гарантированно будут воспроизведены в порядке поступления. Однако для нескольких Queued-объектов, таких гарантий нет. Их никто не обещает воспроизводить в том порядке, в котором эти объекты создавались или освобождались. Если два объекта как-то связаны, могут возникнуть проблемы.

Использование транзакций

Большинство современных распределенных приложений, особенно работающие с базами данных, используют транзакции. Система поддержки событий COM+ тоже поддерживает транзакции. Если приложение издателя участвует в транзакции, можно было бы распространить ее на подписчиков, чтобы они тоже могли участвовать в принятии решения о ее исходе. Например, приложение-издатель производит изменения в базе данных. Существуют некие бизнес-правила, сохраняющие целостность данных. Допустим, эти правила могут в значительной степени зависеть от взаимодействующих компонентов-подписчиков. Издатель, таким образом, избавлен от необходимости сам отслеживать все правила. Вместо этого, издатель инициирует событие в контексте определенной транзакции. Администратор устанавливает подписки на все бизнес-правила, которые должен учитывать издатель. Можно использовать фильтрацию, чтобы избежать непроизводительных затрат. Например, не запрашивать количество детей у лица, не достигшего пятнадцатилетнего возраста. Бизнес-правила подписчиков могут откатить транзакцию, если произойдет сбой хотя бы в одном компоненте-подписчике. Это дешевле и универсальнее, чем отслеживание всех условий единственным компонентом.

Включить поддержку транзакций можно с помощью консоли Component Services. Транзакция издателя будет распространена на подписчиков. Они могут использовать интерфейс IObjectContext для подтверждения или отката транзакции.

Доставка событий нескольким подписчикам может создать ряд проблем, если некоторые из них участвуют в транзакции, а некоторые - нет. Предположим, издатель рассылает событие в контексте определенной транзакции. Подписчик-участник транзакции использует менеджеры ресурсов для изменений в БД, а после этого другой участник транзакции ее отменяет. Изменения, внесенные издателем и первым подписчиком соответственно, тоже откатываются при отмене транзакции. До сих пор все хорошо. Но что, если третий подписчик, в транзакции не участвующий, внесет необратимые изменения в какой-то ресурс, хотя бы старый добрый плоский файл? Когда издатель попытается повторить транзакцию, все задействованные ресурсы будут находиться в состоянии, соответствующем началу первой транзакции, поскольку изменения откатили. А не участвующий в транзакции подписчик, ничего не откатывал. По меньшей мере, он будет обескуражен. Так что не смешивайте транзакционных и нетранзакционных подписчиков.

Безопасность  системы поддержки событий COM+

Система поддержки событий имеет ту же структуру безопасности, что и остальные части COM+. Все правила безопасности, применимые к другим частям COM+, относятся и к системе поддержки событий. Установка событийного класса требует таких же прав, что и для любого другого компонента. Система безопасности также предотвращает ситуацию, в которой некий злобный подписчик заводит миллионы подписок для умышленного затягивания работы, и гарантирует, что никакой неавторизованный пользователь не сможет получить доступ к каталогу подписки. Эти действия разрешены только членам администраторской роли.

Подписчики могут нуждаться в подтверждении аутентичности издателя, чтобы, например, убедиться в том, что организация, предлагающая акции по выгодному курсу действительно является тем, за кого себя выдает. Приложения-подписчики делают это, используя механизм аутентификации COM+ так же, как и любые другие приложения.

Подписчик может быть сконфигурирован как COM+ сервер, исполняющийся как в отдельном процессе, так и как библиотечное приложение, выполняющееся в одном процессе с издателем и разделяющее с ним одно адресное пространство.

Необходимо помнить, что компоненты, разделяющие один процесс, имеют одно адресное пространство и имеют неограниченный доступ к любой области памяти друг друга. Они также работают с одним идентификатором безопасности. Любые исходящие вызовы, производимые компонентом внутри процесса, будут сделаны от имени клиентского процесса. Отношения издателя и подписчика обычно менее доверительны, чем отношения компонентов в других типах COM+ приложений.

Философия дизайна слабосвязанных событий означает, что издатель часто понятия не имеет, кто подписался на его события. Издатель может доверять механизму безопасности защиту информации от доступа к ней посторонних подписчиков. Но это не относится к случаю, когда последние попадают в адресное пространство издателя в виде библиотечных модулей.

Если класс событий и компонент-подписчик оба сконфигурированы как библиотечные приложения, объект-подписчик может быть создан в адресном пространстве издателя. Издатель, скорее всего, будет избегать этого, если только не уверен в подписчике. Такой уровень доверия не свойственен для реального предприятия, хотя и встречается. Если нужно позволить подписчикам, сконфигурированным как библиотечные приложения, пользоваться адресным пространством издателя, установите флажок "Allow in-process subscribers", как показано на Рис. 10. Если этот переключатель не отмечен, объект-подписчик будет создан в отдельном процессе, даже будучи сконфигурирован как библиотечное приложение.

Ограничения

Современная реализация службы событий имеет некоторые ограничения.

Во-первых, система поддержки событий на сегодняшний день не способна производить подписку в COM+ каталоге подписчика. Подписчик должен регистрировать подписки в каталоге COM+ на том компьютере, от которого он хотел бы получать уведомления. Причиной такого положения дел является отсутствие единого хранилища данных обо всех издателях и подписчиках. Можно, разумеется, создать такое хранилище собственноручно, разместив все классы событий и подписки на центральной машине. Издатели тогда будут создавать событийные объекты на центральной машине, а подписчики - получать оповещения от там же расположенной системы событий. Такой подход просто реализовать, но это обойдется в лишний сетевой вызов на событие, запускаемое издателем (поскольку издатель и событийный класс расположены на разных машинах). Центральная машина, в свою очередь, становится слабым звеном. Обеспечить ее страховку горячей заменой можно, например, с помощью Microsoft Clustering Services. Однако, обычно необходимость в рассылке уведомлений о событиях возникает в процессе обработки некоторых бизнес-правил, обычно обрабатываемых на сервере. Это обстоятельство смягчает вышеописанное ограничение.

Во-вторых, на сегодняшний день доставка сообщений в современных системах производится DCOM или Queued-компонентами, механизмами, работающими по принципу "один-к-одному". Это означает, что время и усилия, затрачиваемые на доставку, растут линейно с увеличением числа подписчиков. На момент написания этой статьи приблизительно дюжина подписчиков работает скорее хорошо, чем плохо, а тысячи подписчиков работают скорее плохо, чем хорошо. Сегодняшняя система событий не поддерживает трансляции дейтаграмм, что было бы лучшим способом рассылки уведомлений большому числу подписчиков. Сейчас использовать это можно, создав подписчика, получающего события через DCOM или Queued-компоненты, и пересылающего приходящие данные множеству пользователей как дейтаграммы.

К тому же Queued-компоненты несколько медлительны, а DCOM ставит сервер в слишком сильную зависимость от клиентов. Решением здесь может стать размещение инициирующего рассылку метода в отдельном потоке.

Пример распределенного приложения

Создание и регистрация класса событий

Как мы условились, ради простоты и наглядности разработку всех частей приложения будем стараться делать в VB. Для разработки событийного класса создадим новый проект “ActiveX DLL” под названием TestEventClassPrj. Событийный класс назовем TestEventClass. В соответствие с алгоритмом работы приложения этот класс должен реализовать интерфейс с единственным методом, предназначенным для рассылки сообщений. Назовем этот метод – Send(…). Его описание приведено в Листинге 1.

Листинг 1 Модуль TestEventClass

'Событийный метод Send.
'Параметры:
' - Group – группа подписчиков (тема сообщения),
' - Text – текст сообщения.
Public Sub Send(ByVal Group As String, ByVal Text As String)
End Sub

Первый параметр этого метода задает тему сообщения. Эта возможность введена для того, чтобы подписчики могли подписаться и получать только интересующие их сообщения, а не все подряд. Как видите, по сравнению с прошлым примером, в приложении появляется новая функциональность, реализовать которую помогает система поддержки событий. Фильтрация настраивается с помощью консоли Component Services. Об этом будет рассказано в разделе регистрации приложения-подписчика. Второй параметр – текст сообщения.

Как видно из описания метода Send(…), оба параметра передаются по значению (чтобы удовлетворять требованиям Queued-компонентов). Метод в своем теле не содержит ни одной строчки кода. В этом нет необходимости, так как этот метод никогда не будет вызываться ни издателем, ни системой поддержки событий. Он предназначен лишь для создания библиотеки типов, описывающей событийные объекты, их интерфейсы и методы. Библиотека типов может находиться отдельно или внутри саморегистрирующейся DLL. Именно на основании этого описания COM+ будет создавать событийный объект (Event Object), перехватывающий вызовы издателя и рассылающий уведомления подписчикам.

Для того, чтобы можно было подписаться на события, класс событий должен быть специальным образом зарегистрирован. Для этого:

  1. Создайте COM+ приложение под названием TestEventClass. Для этого в консоли Component Services выделите ветку «COM+ Application», и выберите пункт контекстного меню «New\Application» (см. Рис. 6).

    Рис. 6. Создание приложения, содержащего событийный класс в консоли Component Services.

  2. В появившемся визарде нажмите на кнопку «Create an Empty Application» (см. Рис. 7).

    Рис. 7. Мастер, помогающий создать новое или установить имеющееся приложение

  3. Укажем тип приложения – «Server application» и имя – «TestEventClass», Рис. 8;

    Рис. 8. Создание нового приложения

  4. Выбрав ветку «Components» вновь созданного приложения, из контекстного меню вызовите команду «New\Component»;

  5. В появившемся Мастере нажмите кнопку “Install new event class(es)”. Визард предлагает выбрать DLL библиотеку, содержащую в себе событийный класс. Выберите модуль TestEventClass.dll. Визард сообщит о найденном классе событий. Если этот класс удовлетворяет требованиям системы поддержки событий, визард зарегистрирует его в COM+ каталоге, по сути дела вызвав метод ICOMAdminCatalog.InstallEventClass(…).

Рис. 9. Мастер установки COM+ компонентов

После регистрации компонент будет очень похож на все остальные. Только в панели свойств появятся новые поля, которые администратор сможет настроить, как показано на Рис. 10. Вызвать оную можно из контекстного меню, выбрав пункт Properties.

Рис. 10. Панель свойств событийного класса

В группе свойств LCE (Loosely Coupled Events – слабо связанные события) можно указать, чтобы система поддержки событий распараллеливала оповещение подписчиков. Для этого система будет использовать несколько потоков, что может значительно уменьшить среднее время рассылки уведомлений. Предположим, одно из первых приложений-подписчиков долго обрабатывает оповещение о событии, например, загружая дополнительные библиотеки. Без параллельной рассылки все последующие подписчики будут ждать его. Если включена параллельная рассылка, другие потоки доставят события остальным подписчикам, независимо от «зависшего» потока. Выбор этого переключателя разрешает параллельную рассылку, но не гарантирует ее. Флажок «Allow in-process subscription» относится к системе безопасности, о которой мы поговорим позже. И наконец, поле «Publisher ID» – строковый идентификатор издателя, который позволяет ассоциировать событийный класс с некой удобочитаемой строкой, которая будет использоваться при подписке на события этого класса.

Чтобы издатель не зависел от времени, затрачиваемого на рассылку, снабдим событийный класс Queued-функциональностью. Для этого нужно установить флажки «Queued» и «Listen» в свойствах приложения TestEventsClass (см. Рис. 11). Если эти опции не доступны, то одно из двух, или вы ошиблись, и ваш компонент не соответствует спецификации Queued-компонентов, или на данном компьютере не установлен MSMQ. Флажок «Listen» сообщает системе о том, что если приложение активно, то оно должно обрабатывать приходящие (через MSMQ-очередь) сообщения. Если в это время приложение выполняется, то его лучше выгрузить. Это можно сделать, выбрав пункт “Shut down” в контекстном меню.

Рис. 11. Установка Queued-свойств для приложения TestEventClass

Установка флажка Queued говорит системе событий, что компоненты данного приложения можно вызывать в Queued-режиме, но сам компонент еще не настроен для этого. Чтобы его настроить, надо раскрыть ветку, описывающую TestEventsClass, и в списке интерфейсов выберите событийный интерфейс (_TestEventClass). В его свойствах установите флажок “Queued”, смотри Рис. 12. Если все нормально, то сервис сообщений (MSMQ) создаст необходимые очереди. Для рассылки событий в Queued-режиме необходимо, чтобы все подписчики тоже были зарегистрированы как Queued-компоненты.

Рис. 12. Установка Queued-свойства для событийного интерфейса

Установка этого переключателя позволит создавать событийный объект TestEventClass через строку Queued-моникера, как показано в Листинге 2 приложения издателя, хотя и не отрицает обычного способа – оператором New.

Чтобы и издатели, и подписчики могли использовать наш событийный класс, произведем экспорт приложения TestEventClass. Достаточно произвести экспорт “Proxy” приложения, то есть информации о приложении, а не самого приложения. Proxy содержит в теле MSI-файла библиотеку типов, имя компьютера на котором зарегистрировано серверное приложение и другие свойства. В теле Proxy не содержится самой DLL-библиотеки.

Чтобы экспортировать приложение, вызовите контекстное меню для приложения TestEventClass. Выберите пункт “Export”. В появившемся Мастере укажите полный путь (вместе с именем MSI-файла), куда вы хотите направить экспортируемый файл. Желательно, в каталог на сервере доступный со всех машин в сети. Выберите пункт “Application proxy”, как показано на рисунке Рис. 13.

Рис. 13. Экспорт "Proxy"-приложения событийного класса

После нажатия кнопки «Next >» мастер создаст MSI-файл. Вслед за этим необходимо установить Proxy на компьютеры издателей и подписчиков. Сделать это очень просто. Просто запустите (клавишей Enter) этот файл на тех машинах, где необходимо проинсталлировать Proxy, или воспользуйтесь средствами удаленного администрирования, подключив удаленные компьютеры в консоли Component Services. При этом на каждом удаленном компьютере надо создать новое «COM+»-приложение, как это было описано выше при создании приложения для класса событий в первом пункте, но в момент, когда перед вами возникнет Мастер, выберите не «Create an empty application», а «Install pre-build application(s)», и в появившемся диалоге выбора файлов выбрать «TestEventClass.MSI».

Для компьютера, где будет запускаться приложение-издатель, установка необходима для того, чтобы издатель смог создать объект событий. Для компьютеров-подписчиков регистрация необходима просто потому, что компонент-подписчик реализует интерфейс, описанный в классе событий. Без наличия на компьютере зарегистрированной библиотеки типов, описывающей событийный интерфейс, попросту не получится зарегистрировать компонент-подписчик. Инсталлятор Proxy как раз и хранит в себе библиотеки типов.

Как видите, создать, зарегистрировать и настроить событийный класс довольно просто. Доля программирования здесь минимальна. Основная работа заключается в административных настройках с помощью консоли Component Services.

Создать приложение издателя, инициирующее рассылку уведомлений о событиях, ненамного сложнее.

Разработка приложения-издателя

Сначала опишем порядок работы приложения-издателя. Пользователь вводит сообщение в окне текстового редактора, выбирает из списка тему сообщения, и нажатием кнопки «Send» отсылает сообщение подписчикам (см. Рис. 14). Чтобы обеспечить возможность рассылки сообщений на конкретном компьютере, форма приложения издателя содержит текстовое поле «Event class computer». Пользователь может создавать событийный объект в Queued-режиме. Для этого служит флажок «Queued». Как и в примере прошлого номера, снабдим форму приложения флажком «SendAuto». Если он отмечен, то сообщение будет отсылаться сразу после ввода пользователем очередного символа. При отмеченном «SendAuto», приложение будет работать в более напряженном режиме, что позволит оценить производительность приложения при передаче сообщений в обычном и Queued-режимах.

Приложение-издатель будет инициировать рассылку, вызывая метод Send(…) событийного объекта. Издатель может создавать объект оператором New, функцией CreateObject(…), с помощью обычных моникеров или с помощью Queued-моникеров.

Создайте в среде VB «Standard Exe»-проект под названием PublisherPrj. Чтобы издатель мог «видеть» событийный класс, откройте меню «Project/References…» и добавьте ссылку на библиотеку типов TestEventClassPrj в диалоге «References» (Не забудьте хоть как-то зарегистрировать TestEventClass.dll. Это можно сделать обычным образом или импортировать Proxy приложения, ее содержащую). В принципе, можно было бы обойтись без этой ссылки, воспользовавшись automation-совместимостью нашего событийного объекта, но это очень неудобно для самого разработчика...

Рис. 14. Приложение-издатель

Исходный текст приложения приведен в Листинге 2. Практически весь смысл заключается в строке:

CreateEventObject().Send GroupCB.Text, MessageTB

все остальное – вспомогательная функциональность.

Функция CreateEventObject() создает событийный объект в обычном или Queued-режиме, в зависимости от значения флажка «Queued». В Queued-режиме подстрокой “ComputerName=…” моникера задается имя компьютера, на котором установлен событийный класс. В обычном режиме используется функция CreateObject. Если имя компьютера не задано, оно берется из свойств “Proxy”.

У возвращенного функцией CreateEventObject() событийного объекта сразу вызывается метод Send(…). В обычном (не Queued) режиме это заставляет событийный объект начать рассылку. Значения параметров Group (тема сообщения или группа по интересам) и Text (текст сообщения) берутся из соответствующих полей формы. Сразу после вызова событийного метода объект автоматически освобождается VB. Это вынуждает систему событий (в Queued-режиме) приступить к рассылке. Метод событийного объекта может возвратить один из следующих кодов ошибок:

S_OK – уведомления отправлены всем подписчикам успешно;

EVENT_S_SOME_SUBSCRIBERS_FAILED – уведомление о событии было успешно разослано некоторым (не всем) подписчикам;

EVENT_E_ALL_SUBSCRIBERS_FAILED – ни одному подписчику уведомления разослать не удалось;

EVENT_S_NOSUBSCRIBERS – ни одного подписчика не существует.

В Queued-режиме все вышеперечисленные коды ошибок, кроме S_OK, не доступны.

Напомним, что VB, получая отрицательный код возврата, возбуждает исключительную ситуацию, которую можно обработать посредством оператора On Error XXX. При этом сам код ошибки и ее текстовое описание доступны через объект Err. Положительные коды VB попросту игнорирует, так что, к сожалению, их обработать в VB не получится. К тому же, вышеперечисленные константы описаны только в документации по COM+ Event System, и пока недоступны для разработчиков. Возможно, они появятся в составе очередного SP к Visual Studio, либо вместе с появлением нового SDK для W2K.

Листинг 2. Приложение Publisher.exe

'Инициализация.
Private Sub Form_Load()
    'Начальная установка темы сообщения.
    GroupCB.ListIndex = 0
End Sub

'Обработка события изменения текста в окне
'текстового редактора
Private Sub MessageTB_Change()
    'Если флажок "SendAuto" поднят, то сообщения
    'рассылаются после каждого изменения текста.
    If SendAutoChB Then
        CreateEventObject().Send GroupCB.Text, MessageTB
    End If
End Sub

'Инициирование рассылки сообщения.
Private Sub SendButton_Click()
    CreateEventObject().Send GroupCB.Text, MessageTB
End Sub

'Вспомогательный метод, с помощью которого создается
'событийный объект в Queued или обычном режиме.
Private Function CreateEventObject() As TestEventClass
    If QueuedChB Then
        Set CreateEventObject = GetObject( _
            "queue:ComputerName=" & _
            PublisherComputerTB & _
            "/new:TestEventClassPrj.TestEventClass")
    Else
        Set CreateEventObject = CreateObject( _
           "TestEventClassPrj.TestEventClass", PublisherComputerTB)
    End If
End Function

Создание и регистрация приложения-подписчика

В VB создадим новый проект типа "ActiveX DLL" под названием TestSubscriberPrj. Назовем модуль класса в TestSubscriber.

Чтобы создать объект, получающий события в приложении-подписчике, необходимо реализовать в нем событийный интерфейс – _TestEventClass. Этот интерфейс был создан VB. Он описан в событийном классе TestEventClass.

Добавим ссылку на библиотеку типов TestEventClass в диалоге "References" меню "Project" и реализуем в нем событийный интерфейс. Сделать это можно с помощью ключевого слова Implements. После внесения строки «Implements TestEventClass», VB поможет вам реализовать необходимый метод. Для этого просто надо выбрать из выпадающего списка объектов название интерфейса. При этом во втором списке появится список методов выбранного интерфейса. При выборе во втором списке метода, VB вставит в текст его реализацию. Код реализации интерфейса показан в Листинге 3.

Листинг 3. Модуль TestSubscriber

'Реализация событийного интерфейса
Implements TestEventClass

Private Sub TestEventClass_Send (ByVal Group As String, ByVal Text As String)
    'Инициирование рассылки текстовых сообщений
    'визуальным приемником сообщений - SubscriberMonitor.exe.
    G_Distributor.RaiseEvents Group, Text
End Sub

Внутри метода TestEventClass_Send(…) происходит рассылка сообщений визуальным приложениям-приемникам, подключенным к глобальному SINGLETON-объекту G_Distributor, поддерживающему интерфейс IConnectionPointContainer. Он создается сразу после загрузки библиотеки, как показано в Листинге 4.

Листинг 4. Модуль Globals

Global G_Distributor As New LocalDistributor

Как отмечалось ранее, в VB сложно реализовать SINGLETON-компоненты. Наш локальный поддерживающий события компонент мы назвали LocalDistributor. Он, как и TestSubscriber, является «COM+»-компонентом, экземпляры которого должны создаваться на компьютерах подписчиков. Именно к нему должны подключаться приемники событий в визуальных приложениях. Его реализация очень проста и показана в Листинге 5.

Листинг 5. Модуль LocalDistributor

'Объявление события, рассылаемого подключенным приемникам.
Event Send(ByVal Group As String, ByVal Text As String)

'Инициирование рассылки событий приемникам
Public Sub RaiseEvents(ByVal Group As String, ByVal Text As String)
    'Поочередный вызов подключенных приемников.
    RaiseEvent Send(Group, Text)
End Sub

Посредством оператора RaiseEvent VB производит поочередный вызов событийного метода (Send) у всех подключенных визуальных приложений-приемников.

Внутри одной и той же программы получить ссылку на объект просто. В нашем случае G_Distributor – глобальный объект, что гарантирует его видимость и единственность внутри приложения. Хуже дела обстоят, когда ссылку на существующий экземпляр компонента необходимо получить из другого приложения. Методы New и CreateObject(…) не подходят, потому что создают новые экземпляры объектов. Поэтому нам пришлось создать новый компонент DistribHelper, задачей которого является передача через метод GetDistributor() ссылки на глобальный объект G_Distributor. Код показан в Листинге 6.

Листинг 6. Модуль DistribHelper

Public Function GetDistributor() As LocalDistributor
    Set GetDistributor = G_Distributor
End Function

Через этот метод любое приложение может получить ссылку на единственный SINGLETON-объект. Но упрямый VB все равно упорно создавал новые экземпляры объектов (в отдельных потоках). Этого нам удалось избежать только с помощью установки свойства "Threading model" проекта TestEventSubscriberPrj в "Single Threaded".

После создания приложения-подписчика его необходимо правильно зарегистрировать в консоли Component Services. Нельзя забывать, что регистрация приложения-подписчика возможна, если класс событий, в котором описаны интересующие подписчика события, (в нашем случае это TestEvenClass) к этому времени уже зарегистрирован на компьютере подписчика. Можно даже добавить компонент подписчика в одно приложение с событийным классом. Это упростит экспорт и последующий импорт приложения на компьютерах подписчиков, но здесь кроется один недостаток. В этом случае приложения подписчика и событийного класса должны быть серверными. Это означает, что экспортируемое приложение вместе с подписчиком будет содержать DLL-библиотеку событийного класса. Мы не захотели копировать ее на все компьютеры подписчиков, решив зарегистрировать подписчика как отдельное приложение.

Создадим новое серверное приложение под названием TestEventSubscriber – почти точно так, как мы это делали для приложения TestEventClass. Установку же класса подписчика произведем через кнопку Install new component(s), смотри Рис. 9.

Чтобы не зависеть от подписчика как такового (мечта любого периодического издания!), установим Queued-свойства для приложения подписчика и интерфейса компонента подписчика, см. Рис. 15 и Рис. 16.

Рис. 15. Установка Queued-свойств приложения-подписчика

Рис. 16. Установка Queued-свойства для интерфейса-подписчика

Для распространения приложения-подписчика на другие компьютеры сети, экспортируем его под названием TestSubscriber. Оно должно быть cерверным потому, что выполняется на том же компьютере, на котором установлено. Каталог для MSI-файла желательно указать тот же, что и для приложений событийного класса и издателя – на сервере, смотри Рис. 17.

Рис. 17. Экспорт приложения-подписчика

Установочный MSI-файл запомнит все настройки приложения и входящих в него классов, и включит в себя DLL-библиотеку подписчика.

Установка приложения-подписчика

Для установки приложения-подписчика на удаленных компьютерах достаточно нажать клавишу Enter или дважды щелкнуть мышью на MSI-файле в окне проводника. Сначала установите Proxy-приложение событийного класса. Потом - приложение подписчика. В каталоге COM+ Applications появится новое приложение TestSubscriber. После установки приложения-подписчика можно добавлять в него подписки.

Добавление подписки

Произвести подписку на том же компьютере, где установлен событийный класс, довольно просто. Но в случае удаленных подписчиков будьте внимательны. Не производите подписку для вновь установленного компонента TestSubscriber (хоть это вполне логично). Так события работать не будут. Вспомним, что система поддержки событий не поддерживает распределенного хранилища подписок. Подписываться можно только на том компьютере, где зарегистрирован событийный класс. В нашем случае это компьютер с именем EventClassPC. Добавим подписку на самом компьютере EventClassPC, либо в каталоге этого компьютера в консоли Component Services, благо она позволяет сделать это дистанционно, с компьютера подписчика. Для этого необходимо иметь администраторские привилегии, либо быть участником роли администратора Системного приложения COM+ (COM+ System Application).

Для подписки на события класса вызовите контекстное меню ветки Subscriptions объекта TestSubscriber и выберите пункт меню "New\Subscription…". В панели мастера выберите событийный интерфейс _TestEventClass, как показано на Рис. 18.

Рис. 18. Выбор событийного интерфейса

Если компонент реализует несколько событийных интерфейсов, то с помощью переключателя можно произвести подписку сразу на все события всех интерфейсов (см. Рис. 19). Если выбрать конкретный интерфейс, то будет создана подписка на все его событийные методы. Если выбрать конкретный метод, то приниматься будут события, связанные только с ним. Если нужно подписаться только на определенные интерфейсы или методы, то придется создавать по отдельной подписке на каждый интерфейс или метод.

После выбора интерфейса или метода, открывается следующая панель мастера. В ней отображаются ProgID всех зарегистрированных в COM+ каталоге событийных классов, поддерживающих необходимый интерфейс, смотри Рис. 19.

Рис. 19. Выбор зарегистрированного событийного класса

Выберите подходящий класс, либо, отметив флажок, используйте все доступные. После этого мастер предложит ввести имя подписки (см. Рис. 20). Однако по умолчанию подписка не активизирована. Активизировать ее можно с помощью флажка "Enable" в той же панели.

Рис. 20. Панель опций подписки

После нажатия кнопки “Next >” подписка будет добавлена в COM+ каталог.

Новая подписка появится в консоли Component Services. Единственное, чего не сделал мастер - не позволил нам указать имя компьютера подписчика. Внесем его в поле "Server Name" панели свойств подписки (см. Рис. 21). Если нужно получать уведомления в Queued-режиме, отметьте соответствующий флажок.

Отдельного обсуждения заслуживает поле “Filter criteria”. Это и есть критерий фильтрации, о котором мы говорили выше. Система поддержки событий дает подписчику возможность определить строку критерия фильтрации как часть подписки. Напомним, что на основании этих критериев система поддержки событий будет принимать решения, кому из подписчиков посылать уведомления, а кому нет. В критерии можно применять операции сравнения, булевы операции OR, AND и NOT. Поддерживаются и вложенные скобки.

Рис. 21. Свойства подписки

Приложение подписчика создано и способно принимать уведомления. Подписки произведены. Но для визуализации текстовых сообщений нужно еще одно приложение - поскольку персистентным подписчиком может быть только COM+ приложение, а оно не может иметь графического интерфейса.

Визуальный приемник сообщений

Программа визуального приемника служит для приема и отображения текстовых сообщений. На одном компьютере может быть запущено несколько таких программ. Все они подключаются к единому объекту LocalDistributor, реализующему обычный для VB событийный механизм (ActiveX Events), через который и получают текстовые сообщения.

Создайте проект типа “Standard EXE” под названием SubscriptionMonitorPrj. Не забудьте добавить ссылку на библиотеку типа TestSubscriber. Вид приложения показан на Рис. 22.

Рис. 22. Визуальный приемник сообщений

Исходный текст приложения показан в Листинге 7.

Листинг 7. Приложение SubscriptionMonitor.exe

'Объявление SINGLETON-класса.
Dim WithEvents Distributor As LocalDistributor

Private Sub Form_Load()
    'Создание вспомогательного объекта.
    Dim DistribHelper As New DistribHelper
    'Получение SINGLETON-объекта, источника событий.
    Set Distributor = DistribHelper.GetDistributor()
End Sub

Private Sub Distributor_Send(ByVal Group As String, ByVal Text As String)
    'Отображение информации.
    GroupTB = Group
    MessageTB = Text
End Sub

Все довольно просто. Для распространения приложения на другие компьютеры скопируем модуль SubscriberMonitor.exe в тот же сетевой каталог, куда мы экспортировали приложения событийного класса и подписчика.

Издательская фильтрация исходящих вызовов

Задание выражения "Filter criteria" в свойствах подписки - простейший способ управления процессом рассылки. Этот подход чрезвычайно упрощает создание и развертывание приложений, но может оказаться недостаточно гибким. Подобная система не предполагает возможности управления очередностью рассылки. Она не дает никакого шанса издателю отказаться от доставки уведомления определенному подписчику. А ведь такие решения – распространенная часть бизнес-логики издателя. Например, издатель журнала прежде чем отправить издание подписчику, мог бы захотеть проверить по бухгалтерии, оплатил ли подписчик стоимость подписки. Другой пример: агентство новостей может захотеть рассылать информацию подписчикам в определенном порядке, допустим, кто-то из подписчиков заплатил больше за первоочередное оповещение. Функциональность системы поддержки событий по умолчанию не может справиться с такими ситуациями. Для максимальной гибкости вам нужен способ влияния издателя на процесс рассылки событий.

Издатель может управлять процессом рассылки событий через интерфейс IPublisherFilter, чьи методы описаны в Таблице 1.

Таблица 1

Методы интерфейса IMultiInterfacePublisherFilter

Метод

Описание

Initialize

Инициализация объектного фильтра.

PrepareToFire

Подготавливает издательский фильтр к рассылке уведомлений подписчикам с помощью интерфейса IFiringControl. Этот интерфейс реализован в событийном объекте и может быть получен от него.

Особенно интересен метод IFiringControl.GetSubscriptions(…), возвращающий перечислитель, который можно использовать для исследования коллекции свойств интерфейсов подписок IEventSubscription, перечисленных в Таблице 2.

Таблица 2

Свойства интерфейса IEventSubscription

Свойство

Описание

SubscriptionID

GUID подписки.

SubscriptionName

Имя подписки (Читаемая строка).

PublisherID

GUID издателя.

EventClassID

CLSID событийного класса.

MethodName

Имя событийного метода, вызываемого системой поддержки событий у подписчика.

SubscriberCLSID

CLSID компонента-подписчика. Если не пуст, подписка является постоянной, при этом значение свойства SubscriberInterface должно быть NULL (Nothing для VB-программистов). Это свойство работает вместе со свойством MachineName для постоянных подписок.

SubscriberInterface

Указатель на интерфейс подписчика. Если не равен нулю, то подписка является временной и свойство SubscriberCLSID должно быть пустым.

PerUser

Если равно TRUE, то подписка доступна только для пользователя с идентификатором, задаваемым свойством OwnerSID. Компонент подписчика должен создаваться на том же компьютере, где произведена подписка. По умолчанию – FALSE.

OwnerSID

Идентификатор пользователя, создавшего подписку.

Enabled

Если TRUE, то подписка доступна и события от издателя будут получены подписчиком. Если FALSE, то события не будут получены. При этом подписка останется в списке и будет доступна для администрирования.

Description

Описание подписки (Читаемая строка).

MachineName

Имя компьютера, на котором будет активизирован подписчик. Может быть установлено, если заполнено свойство SubscriberCLSID. Для временных подписок это свойство должно быть пустым.

InterfaceID

IID событийного интерфейса.

Фильтр определяет, нужно ли оповещать конкретного подписчика. С помощью фильтра можно задать и порядок оповещения. Коллекция подписок, поддерживаемая перечислителем, обновляется автоматически. Издатель может установить издательский фильтр с помощью вызова метода SetPublisherFilter интерфейса IEventControl. Этот интерфейс можно получить, запрашивая событийный объект, но такой способ доступа вызывает несколько проблем. Во-первых, это не будет работать, если сам класс событий является Queued-компонентом, так как интерфейс не поддерживает Queued-стандарта. Во-вторых, этот способ хорош, если "вкомпилирован" в издательскую программу. Если же исходного кода издателя нет, то изменить ничего не получится. Таким способом нельзя изменить поведение фильтра без перестройки собственно издательской программы.

Система поддержки событий COM+ предоставляет другой способ издательской фильтрации, позволяющий решить эти проблемы. Это использование отдельного компонента - издательского фильтра. Издательский фильтр - это компонент COM+, инсталлируемый после событийного объекта. Перенос логики фильтрации в издательский фильтр означает, что он будет работать с Queued-компонентами и программным обеспечением сторонних разработчиков. Нужно записать в «COM+»-каталог CLSID издательского фильтра - как свойство класса событий MultiInterfacePublisherFilterCLSID. Консоль Component Services не содержит пользовательского интерфейса для этих целей; так что для такого случая придется писать собственные средства администрирования.

Когда издатель создает событийный объект, система событий считывает CLSID фильтра, назначенного этому классу событий и создает объект-фильтр. Если объект-фильтр не может быть создан, создания событийного объекта тоже не произойдет. Класс фильтра должен реализовать интерфейс IMultiInterfacePublisherFilter (см. Таблицу 1). Создав объект-фильтр, система событий вызовет метод IPublisherFilter::Initialize(…), передав интерфейс IDispatch фильтра (у которого запрашивается интерфейс IMultiInterfaceEventControl). Этот интерфейс передается только один раз, в методе Initialize(…). Используйте его для получения нужной информации (например, для получения списка подписок). Когда издатель инициирует рассылку оповещений, система событий автоматически вызовет метод фильтра IMultiInterfacePublisherFilter::PrepareToFire(…). Первый параметр определяет IID событийного интерфейса, второй - имя рассылаемого метода, а третий – указатель интерфейса типа IFiringControl. У этого интерфейса есть один метод с именем FireSubscription(…). Он используется, чтобы приказать системе сообщений оповестить одного подписчика. Вызов этого метода активирует все стандартные механизмы доставки, включая рассмотренные выше параметры фильтрации.

Пример приложения, устанавливающего издательский фильтр для нашего событийного класса, демонстрируется на Рис. 23, а исходный текст в Листинге 8.

Рис. 23. Установка издательского объектного фильтра

Листинг 8. Программа установки издательского объектного фильтра

'Функция, возвращающая интерфейс установленного
'событийного класса в COM+ каталоге.
Private Function GetComponent(ByRef Components As COMAdminCatalogCollection) As COMAdminCatalogObject
    'Корневой каталог Component Services.
    Dim Catalog As New COMAdminCatalog
    'Каталог приложений.
    Dim Apps As COMAdminCatalogCollection
    'Получить каталог приложений.
    Set Apps = Catalog.GetCollection("Applications")
    'Считывание списка приложений.
    Apps.Populate
    
    'Найти приложение по его имени.
    For Each AppObject In Apps
        If AppObject.Name = AppNameTB Then
            GoTo Continue_1:
        End If
    Next
    'В случае неудачи - выход из функции.
    Exit Function

Continue_1:

    'Получение подкаталога компонентов в приложении.
    Set Components = Apps.GetCollection( _
        "Components", AppObject.Key)
    If Components Is Nothing Then Exit Function

    'Считать содержимое каталога.
    Components.Populate
    
    'Поиск событийного класса по его имени
    For Each Comp In Components
        If Comp.Name = EventClassProgID_TB Then
            GoTo Continue_2
        End If
    Next
    'В случае неудачи - выход из функции.
    Exit Function

Continue_2:
    Set GetComponent = Comp
End Function

'Установка или снятие CLSID фильтра
'в зависимости от переключателя EnableFilterChB.
Private Sub EnableFilterChB_Click()
    Dim Components As COMAdminCatalogCollection
    Dim Component As COMAdminCatalogObject
    'Получение событийного класса.
    Set Component = GetComponent(Components)
    If Component Is Nothing Then
        MsgBox "EventClass not found"
        Exit Sub
    End If
    
    If EnableFilterChB Then
        'Установка объектного фильтра.
        If Empty = FilterCLSID_TB Then
            EnableFilterChB = False
        End If
    
        On Error Resume Next
        Component.Value("MultiInterfacePublisherFilterCLSID") = FilterCLSID_TB.Text
        If Err.Number <> 0 Then
            MsgBox "Can't install filter, error: " & Err.Description
            EnableFilterChB = False
        Else
            MsgBox ("Publisher filter installed successfully")
        End If
    Else
        'Удаление объектного фильтра.
        Compo-nent.Value("MultiInterfacePublisherFilterCLSID") = ""
        MsgBox ("Publisher filter uninstalled successfully")
    End If
    'Запись изменений в COM+ каталог.
    Components.SaveChanges
End Sub

Private Sub Form_Load()
    Dim Components As COMAdminCatalogCollection
    Dim Component As COMAdminCatalogObject
    'Получение событийного класса.
    Set Component = GetComponent(Components)

    If Component Is Nothing Then
        MsgBox "EventClass not found"
    End If

    'Установка переключателя EnableFilterChB.
    FilterCLSID_TB = Component.Value("MultiInterfacePublisherFilterCLSID")
    If FilterCLSID_TB <> Empty Then 'Component.Value( _
             "MultiInterfacePublisherFilterCLSID") Then
        EnableFilterChB.Value = Checked
    Else
        EnableFilterChB.Value = Unchecked
    End If
End Sub

Теперь опишем процесс создания объектного фильтра. Стараясь соблюсти чистоту эксперимента, мы попытались и его реализовать в VB. На наш взгляд, это было бы очень красиво – создавать распределенные приложения в среде VB, используя возможности Queued-компонентов и системы поддержки событий. Практически все компоненты в системе событий automation-совместимы. Это позволяет создавать любую часть распределенного приложения на таких языках, как VB или Java. Но что-то у Microsoft не сложилось, и ввели они в метод IMultiInterfacePublisherFilter::PrepareToFire(…) параметр iid, тип которого (GUID) не поддерживается VB. Поэтому пришлось создавать фильтр на VC++ с помощью ATL. Хоть ATL и значительно облегчает создание компонентов, это все же на порядок сложнее, чем в VB. В этом можно убедиться, оценив сложность и читаемость кода, содержащегося в Листинге 9.

Чтобы создать объект фильтра, необходимо в нем реализовать интерфейс IMultiInterfacePublisherFilter. Для этого создайте ATL-проект под именем FilterPrj. В нем с помощью Мастера ATL создайте новый компонент под названием Filter. Вызвав контекстное меню класса CFilter, выберите пункт Implement Interface… В панели диалога отметьте библиотеку типа COM+ Event System. Выберите интерфейс IMultiInterfacePublisherFilter, нажмите OK, и интерфейс будет реализован.

Листинг 9. Пример реализации объектного издательского фильтра

// Filter.h : Declaration of the CFilter

#ifndef __FILTER_H_
#define __FILTER_H_

#include "resource.h"       // main symbols

//Импорт интерфейса ImultiInterfacePublisherFilter.
#import "D:\WIN2000\System32\es.dll" raw_interfaces_only, \
    raw_native_types, no_namespace, named_guids

//Реализация интерфейса ImultiInterfacePublisherFilter.
////////////////////////////////////////////////////////
// CFilter
class ATL_NO_VTABLE CFilter : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CFilter, &CLSID_Filter>,
   public IDispatchImpl<IMyFilter, &IID_IMyFilter, &LIBID_FILTER1Lib>,
   public IMultiInterfacePublisherFilter
{
public:
   CFilter() {
      //Проверка на создание.
      Beep(1000, 100);
   }

DECLARE_REGISTRY_RESOURCEID(IDR_FILTER)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CFilter)
   COM_INTERFACE_ENTRY(IMyFilter)
   COM_INTERFACE_ENTRY(IDispatch)
   COM_INTERFACE_ENTRY(IMultiInterfacePublisherFilter)
END_COM_MAP()

// IMyFilter
//Смарт указатель на перечислитель подписок.
CComPtr <IEnumEventObject> pSubs;

public:
// IMultiInterfacePublisherFilter
   STDMETHOD(Initialize)(IMultiInterfaceEventControl * pEIC) {
      //строка IID событийного интерфейса, получена
      //из панели свойств событийного интерфейса в CS консоле.
      GUID EventIID;
      HRESULT hr = CLSIDFromString(L"{9F79CCBA-3D44-49E6-B110-FAC734689459}", &EventIID);
      if (FAILED (hr))
         return hr;

      //Получить интерфейс коллекции подписок.
      CComPtr <IEventObjectCollection> pSubCol;
      hr = pEIC->GetSubscriptions (&EventIID, NULL, NULL, NULL, &pSubCol);
      if (FAILED (hr))
         return hr;

      //Получить интерфейс перечислителя подписок.
      hr = pSubCol->get_NewEnum (&pSubs);
      if (FAILED (hr))
         return hr;

      return S_OK;
   }

   STDMETHOD(PrepareToFire)(GUID * iid, BSTR methodName, IFiringControl * firingControl) {
      //Ограничимся для простоты сотней подписчиков.
      CComPtr<IEventSubscription> SubscrArr[100];
      //Количество подписчиков.
      ULONG Cnt;
      //Инициализация перечислителя.
      pSubs->Reset ();
      //Получение массива указателей на интерфейсы подписчиков.
      HRESULT hr = pSubs->Next(100, (IUnknown**)SubscrArr, &Cnt);
      if (FAILED (hr))
         return hr;
      
      if (Cnt == 0) 
         return S_OK; //EVENT_S_NOSUBSCRIBERS;

      //Результат рассылки.
      HRESULT firing_hr = S_OK;
      //Хотя бы один подписчик принял оповещение.
      BOOL AtLeastOne = FALSE;
      //Оповещение подписчиков.
      //В этом месте разработчик, может управлять
      //рассылкой событий.
      for (ULONG i = 0; i < Cnt; i++) {
         hr = firingControl->FireSubscription (Sub-scrArr[i]);
         if (FAILED (hr))
            firing_hr = E_FAIL; //EVENT_S_SOME_SUBSCRIBERS_FAILED;
         else
            AtLeastOne = TRUE;
      }

      //Обработка ошибок.
      if (AtLeastOne)
         return S_OK; //firing_hr;
      else
         return E_FAIL; //EVENT_E_ALL_SUBSCRIBERS_FAILED;

      return S_OK;
   }
};
#endif //__FILTER_H_

Строку CLSID объектного фильтра, используемого в приложении SetFilter.exe (см. Листинг 8), мы взяли из файла Filter.rgs. Этот файл Мастер создания ATL-объектов создает автоматически при создании объекта.

Особенности технологии

Хочется отдать должное Microsoft. Благодаря W2K, мы создали распределенное приложение, приложив минимум усилий. Мы даже можем серьезно менять логику его работы с помощью фильтров и без перепрограммирования. Усилия по распространению приложения тоже невелики. Все вызовы, как от издателя к событийному объекту, так и от последнего к подписчикам, могут быть асинхронными, что обеспечено Queued-технологией. Операционная система берет на себя самую сложную часть алгоритма – реализацию сетевого механизма уведомлений и обеспечение асинхронности. Еще вчера создание приложения с таким набором свойств было задачей для серьезного коллектива разработчиков.

Приятно обрадовало повышение производительности работы Queued-компонентов по сравнению с W2K Beta 3, хотя для критических ко времени исполнения приложений и этого явно недостаточно.

Всякое хорошее нововведение имеет недостатки. Microsoft явно перегнул палку с безопасностью. Конечно же, высокая степень защиты - это хорошо, но не мешало бы дать возможность администратору разрешать или запрещать отдельным пользователям самостоятельно производить подписку. Иначе могут возникнуть сложности при установке приложений на отдельных клиентских компьютерах, так как администратору придется вручную создавать подписки для них. Количество действий администратора перемножается на количество рабочих мест, а в большом распределенном приложении может содержаться множество событийных классов.

Надежность работы Queued-компонентов с системой поддержки событий оставляла желать лучшего. Они то работали, то нет. Для серьезных приложений это довольно опасный симптом. Спишем это на Release Candidate 3 Build 2178. Будем верить, что после выхода W2K все заработает надежно.

Выводы

Возможность посылать уведомления о событиях заинтересованным сторонам очень полезна в распределенных вычислениях. Связь издателей и подписчиков долго было проблемой для разработчиков распределенных приложений. Система событий COM+ в Windows 2000 даст готовую инфраструктуру, позволяющую издателям оповещать подписчиков очень просто и без написания большого количества кода.

Но, как и все первые версии, эта версия является экспериментальной и содержит много недоработок. Со временем они, скорее всего, будут устранены. Пока же хотелось бы, чтобы разработчики этой системы больше обращали внимание на полноту и качество реализации базовых возможностей, а не пытались создавать все новые и новые дополнительные возможности.

Рассылке в Queued-режиме неплохо бы работать побыстрее и более качественно реализовывать возможности работы при временных отключениях от сети. Не помешала бы широковещательная рассылка уведомлений (например, на базе UDP-датаграмм), рассчитанная на тысячи подписчиков. Такая рассылка, с одной стороны, не должна отнимать времени у издателя, а с другой - не должна перегружать сеть.

Хотелось бы, чтобы все возможности по управления подпиской были доступны из высокоуровневых языков типа VB. На сегодня это не совсем так. Например, мы не смогли создать издательский фильтр на VB.

Хотя основы механизма событий COM+ уже разработаны, это все же только начало большого пути. Система поддержки событий не только поможет разработчикам распределенных приложений сосредоточиться на бизнес-логике своего предприятия и заработать тем самым больше денег. Она к тому же позволит свалить на Microsoft все свои проблемы и трудности! Пожелаем же ее создателям удачи в их нелегком, но благородном труде.

А. Новик.


Copyright © 1994-2016 ООО "К-Пресс"