COM+ – промежуточная среда для создания распределенных систем, действующих в локальной сети. Она разрабатывается фирмой Microsoft с конца 90-х годов и впервые появилась в составе операционной системы Microsoft Windows 2000. Основной целью разработки среды COM+ было создание инфраструктуры для разработки распределенных систем автоматизации предприятия. Основные достоинства среды COM+:
Среда COM+ управляет ходом выполнения объектов COM+, являющимися экземплярами так называемых компонент COM+. Набор связанных компонент COM+, находящихся в одной динамической библиотеке, называется приложением COM+. Приложение COM+ состоит из набора компонент и ролей для доступа к ним. Сведения о зарегистрированных приложениях хранятся в каталоге COM+.
Приложения COM+ бывают двух видов: библиотечные и серверные. Экземпляры компонент библиотечных приложений выполняются в том же процессе, что и использующий их клиент, компоненты серверного – в отдельном потоке сервера, возможно выполняющимся на удаленном компьютере. Только серверные приложения могут использоваться удаленно путем регистрации в каталоге COM+ на компьютере клиента посредника приложения COM+. После установки посредников использование удаленных серверных компонент COM+ не отличается от использования локальных серверных компонент. Понятие посредник ( proxy ) используется в COM+ в двух различных смыслах – запись в каталоге COM+, связанная с некоторым приложением COM+ на удаленном компьютере, и в том же смысле, что и посредник при удаленном вызове.
Поскольку библиотечные приложения COM+ не используют механизм удаленных вызовов и могут использоваться только в адресном пространстве клиента, то их рассмотрение не относится к данному курсу, и далее речь пойдет исключительно о серверных приложениях COM+.
На рис. 6.1 показана архитектура среды COM+ при использовании серверных приложений. Объекты COM+ являются экземплярами компонент COM+, зарегистрированных в каталоге. Заглушка на стороне серверного процесса называется в COM+ перехватчиком ( interceptor ).
(рис 6.1) Архитектура среды COM+Каталог COM+ каждого компьютера содержит список зарегистрированных на
компьютере локальных приложений COM+, а также список установленных
посредников для связи с приложениями удаленных компьютеров. Каталог устроен
иерархически, в виде дерева. Например, узел с описанием приложения COM+ содержит
узел со списком входящих в него компонент COM+ и узел со списком ролей
приложений. Управление каталогом COM+ происходит при помощи оснастки comexp.msc или программно, используя классы библиотеки comadmin.dll. Подробнее состав каталога описан в приложении I.
Поскольку среда COM+ реализует свое собственное управлением исполняемым внутри нее кодом, то существует понятие контекста COM+, который представляет собой окружение объекта COM+. Контекст закрепляется за объектом в момент его активации и сохраняется до его деактивации. При создании контекста учитываются атрибуты необходимости транзакции и синхронизации, установленные в активируемой компоненте COM+, а так же текущий контекст в момент активации объекта.
Целью создания промежуточной среды COM+ являлось предоставление компонентам COM+ набора сервисов, облегчающему создание надежной и масштабируемой распределенной системы. Начиная с Windows 2003/XP SP2, часть этих сервисов может быть доступна и без создания компонент COM+, путем использования так называемых сервисов без компонент.
Среда COM+ позволяет исключить проблемы с синхронизацией при обслуживании
запроса клиента путем так называемых активностей. Активность начинается в момент
создания клиентом COM+ объекта и заканчивается при его удалении. В течении
активности клиент вызывает методы компоненты COM+, в ходе которых она может
создавать другие объекты COM+, в том числе расположенные на удаленных
компьютерах, и вызывать их методы. При этом COM+ гарантирует, что в течение
одной активности в каждый момент выполняется метод только одного COM+ объекта
из всех участвующих в активности. Таким образом, активность представляет собой
некий логический
| Настройка синхронизации компоненты COM+ | Создатель объекта участвует в активности | Создатель объекта не участвует в активности |
|---|---|---|
| Не поддерживается (Not Supported) | Вне активности | Вне активности |
| Поддерживается (Supported) | Активность создателя | Вне активности |
| Требуется (Required) | Активность создателя | Новая активность |
| Требуется новая (Requires new) | Новая активность | Новая активность |
Следует учитывать, что настройки участия в транзакции компоненты влияют на ее настройки синхронизации, о чем будет сказано далее.
COM+ поддерживает динамическую балансировку нагрузки. Для этого необходимо создать кластер COM+ на базе серверной версии операционной системы Microsoft Windows (Windows 2000 Advanced Server и последующих). Кластер COM+ состоит из нескольких серверов и распределяющим между ними запросы маршрутизатором COM+. Для повышения надежность такой системы могут применяться способы быстрой замены маршрутизатора при его выходе из строя на базе Windows Clustering Services. Таким образом, среда COM+ позволяет при наличии необходимости достаточных финансовых ресурсов создать хорошо масштабируемую систему без уникальной точки сбоя.
Среда COM+ поддерживает два вида активации объектов – активация по требованию клиента и активация на время единственного вызова (называемая в среде COM+ JIT активацией) с возможностью использования пула объектов. Поскольку распределенные транзакции поддерживаются только для второго типа активации, в дальнейшем будет рассматриваться только он.
Одним из основных достоинств среды COM+ является поддержка распределенных транзакций на базе координатора распределенных транзакций. Настройки транзакции компоненты COM+ влияют на настройки синхронизации и активации. Любая включенная настройка транзакций, отличная от Not supported, требует использования JIT активации и ограничивает выбор настройки синхронизации не более чем вариантами Required и Requires new. При активации объекта среда COM+ определяет необходимость использования транзакций в соответствии с таблицей 6.2.
| Настройка транзакции компоненты COM+ | Создатель объекта участвует в транзакции | Создатель объекта не участвует в транзакции |
|---|---|---|
| Не поддерживается (Not Supported) | Вне транзакции | Вне транзакции |
| Поддерживается (Supported) | Транзакция создателя | Вне транзакции |
| Требуется (Required) | Транзакция создателя | Новая транзакция |
| Требуется новая (Requires new) | Новая транзакция | Новая транзакция |
Если при создании объекта обнаружена потребность в создании новой транзакции, среда COM+ создает ее с помощью координатора транзакций и данный объект считается корнем транзакции (рис. 6.2). Поскольку требующие транзакцию объекты используют активацию одного вызова, то транзакция не может существовать дольше, чем один вызов метода корневого объекта клиентом COM+. В транзакции могут участвовать службы, имеющие свой менеджер ресурсов, в частности MSMQ и MS SQL. Для многих ресурсов при необходимости можно создать свой компенсирующий менеджер ресурсов.
(рис 6.2) Транзакция COM+Каждый из участвующих в транзакции объектов должен в конце выполнения своего метода сообщить об успешности транзакции или ее неудаче. В случае, если все объекты объявили об успешности транзакции, служба COM+ оповещает все участвующие в транзакции внешние службы о необходимости сделать произведенные в рамках транзакции изменения постоянными (рис. 6.3).
(рис 6.3) Успешное завершение транзакции COM+
Хотя среда MSMQ может использоваться в рамках транзакции COM+, это приводит к одновременному использованию двух технологий удаленного взаимодействия. Вероятно часто было бы удобно скрывать использование MSMQ путем создания компонент COM+, поддерживающих асинхронные коммуникации. Поэтому для реализации асинхронного удаленного вызова в промежуточной среде COM+ существуют так называемые ожидающие компоненты ( queued componenets ), которые прозрачно используют MSMQ. Использование такой компоненты подобно асинхронному удаленному вызову (рис. 6.4).
(рис 6.4) Использование ожидающих компонент COM+Аналогичным образом происходит получение результата вызова удаленной компоненты: на сервере создается протоколист, а на клиенте – слушатель и исполнитель. Однако в этом случае до начала использования удаленной компоненты клиенту следует создать вызываемый объект ( call-back object ), который будет принимать ответ от сервера через исполнителя, и передать ссылку на такой объект (точнее, на его исполнителя), серверу (рис. 6.5).
(рис 6.5) Взаимодействие с отложенной компоненты
Среда COM+ позволяет компонентам подписаться на события, создаваемые какими либо объектами COM+ (издателями событий). При этом подписчики и издатели могут не знать о существовании друг друга, но должны знать о существовании общего интерфейса COM+, который используется для публикации событий. Наравне с отложенными компонентами, слабо связанные события предоставляют второй вариант реализации асинхронного обмена данными в среде COM+.
Определение политики управления доступом для компонент COM+ осуществляется на
основе
Прежде чем назначать роли группам, необходимо понять политику обеспечения безопасности приложения. В идеале названия ролей должны соответствовать категориям пользователей, которым они будут назначены. Кроме того, с помощью оснастки служб компонентов можно получить описание каждой роли, в котором могут содержаться сведения о том, какие пользователи могут назначаться на данную роль.
Среда COM+ была создана до технологии .NET, поэтому она работает с
неуправляемым кодом и не является носителем исполняемой среды CLR. Для
использовании сервисов COM+ из .NET Framework необходимо получить возможность
использовать управляемый код в контексте COM+. Для решения данной проблемы
была создана достаточно сложная схема взаимодействия сред CLR и COM+,
основанная на понятии компоненты, использующей сервисы COM+
( serviced component ), называемой далее обслуживаемой (средой
COM+) компонентой. Такая компонента состоит из объекта управляемого кода,
принадлежащему наследованному от System.EnterpriseServices.ServicedComponent классу.
Благодаря наследованию от ServicedComponent при исполнении методов этого
объекта имеется доступ к контексту COM+. Использование таких компонент
упрощенно показано на рис. 6.6.
(рис 6.6) Взаимодействие COM+ и CLR при использовании серверных приложенийМаршализация параметров методов обслуживаемой компоненты при использовании
происходит под управлением службы .BinaryFormatter ), а среда COM+ используется только для реализации
своих сервисов.
Как видно из рис. 6.6, обслуживаемая компонента
.NET Framework не является, строго говоря, компонентой COM+. Аналогично и
промежуточная среда .NET Enterpsise Services является не новым названием среды
COM+, а средством использования сервисов COM+ в управляемом коде. Хотя часто
можно упрощенно считать, что обслуживаемые компоненты – это компоненты COM+
на управляемом коде, но при разработке обслуживаемых компонент существуют
случаи, когда желательно понимать взаимосвязь CLR и COM+. Например, если при
вызове метода управляемой компоненты происходит ошибка в момент выполнения
неуправляемого кода среды COM+, то полученная от посредником COM+ информация
преобразуется в исключение типа System.Runtime.InteropServices.COMException.
В качестве текста сообщения в этом исключении фигурируют ошибки среды COM+, достаточно
малопонятные с точки зрения управляемого кода, например такие.
Unhandled Exception: System.Runtime.InteropServices.COMException (0x8000FFFF): Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)) Unhandled Exception: System.Runtime.InteropServices.COMException (0x8004D082): Exception from HRESULT: 0x8004D082
При возникновении исключения в управляемом коде обслуживаемой компоненты
происходит обычное исключение .NET, которое, тем не менее, должно уметь
маршализироваться по значению с помощью .
Обслуживаемая компонента .NET Framework является объектом класса,
наследованным от System.EnterpriseServices.ServicedComponent,
и отвечающим следующим требованиям:
public ;Статические методы в классе компоненты возможны, но они выполняются в контексте клиента, и имеют доступа к контексту COM+ клиента, если таковой существует.
Класс сервисной компоненты должен быть объявлен в сборке, которая может быть
зарегистрирована в качестве приложения COM+. Такая сборка не должна иметь классов
общего вида со спецификатором public, и должна быть подписана.
Класс обслуживаемой компоненты может иметь атрибуты из пространства имен System.EnterpriseServices, связанные с транзакцией
( TransactionAttribute ), активацией
( JustInTimeActivationAttribute и ObjectPoolingAttribute )
и синхронизацией ( SynchronizationAttribute ). Как упоминалось ранее,
поддержка транзакции автоматически означает JIT-активацию и участие в активности,
тем не менее следует перечислять атрибут JIT активации явно.
Рассмотрим код простейшей обслуживаемой компоненты.
// Файл SampleComponent.cs
using System;
using System.EnterpriseServices;
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationName("Serviced Component Example 01")]
[assembly: Description("Sample of .NET serviced component.")]
[assembly: ApplicationActivation(ActivationOption.Server)]
Эти атрибуты задают свойства сборки, которая затем будет зарегистрирована в
качестве приложения COM+. Атрибут ApplicationActivation
позволяет выбрать серверный или библиотечный тип приложения.
namespace ServicedComponentSample
{
[JustInTimeActivation]
[Transaction(TransactionOption.Required)]
[ObjectPooling(Enabled=true, MinPoolSize=5, MaxPoolSize=10)]
public class SampleComponent: ServicedComponent
{
Объявленный выше класс компоненты, как видно из атрибутов, требует наличия транзакции и использует JIT активацию с пулом объектов
public SampleComponent()
{
}
Для использования в пуле объект должен быть возвращен в свое первоначальное
состояние (деактивизирован). Для поддержки режима активации одного вызова
компонента не должна иметь состояния, сохраняемого между вызовами ее методов
клиентом, и таким образом часто она не требует каких-либо действий для перевода в
первоначальное состояние. При необходимости можно перекрыть виртуальные методы Activate и Deactivate, вызываемые при операциях
активации и деактивации, и производить в них сброс состояния объекта.
protected override void Activate()
{
}
protected override void Deactivate()
{
// Здесь при необходимости можно очистить
// состояние объекта и освободить ресурсы
}
Метод CanBePooled возвращает true,
если объект может быть возвращен в пул объектов после деактивации, иначе он уничтожается.
protected override bool CanBePooled()
{
// Подтверждает возможность помещения объекта в пул
return true;
}
Атрибут System.EnterpriseServices.AutoCompleteAttribute применяется
для автоматического участия методов в транзакции. Если метод завершается
нормально, то считается, что он подтвердил успешное завершение транзакции, при
возникновении же в методе не пойманного исключения транзакция будет отменена.
[AutoComplete]
public void Do()
{
}
}
}
// Файл SampleComponent.cs
Альтернативный AutoComplete вариант использования транзакций
заключается в вызове статических методов класса ContextUtil. Этот
класс используется для доступа и изменения контекста COM+, связанного с
обслуживаемой компонентой. Метод ContextUtil.SetComplete сообщает
COM+ об успешном завершении метода, а ContextUtil.SetAbort
предопределяет отмену транзакции. Используются они обычно следующим образом.
try
{
...
ContextUtil.SetComplete();
}
catch()
{
ContextUtil.SetAbort();
}
Постоянное использование методов ContextUtil приводит к коду,
который будет сложно перенести из компоненты COM+ в класс, выполняемый вне
промежуточной среды COM+. Для решения этой проблемы следует либо использовать
методы класса, оборачивающего методы ContextUtil, либо, в крайнем
случае, атрибут AutoComplete, который игнорируется для обычных классов.
Сборка, содержащая описание одного или нескольких классов компонент, должна быть
зарегистрирована как приложение COM+ в каталоге COM+. Для этого сборка должна
быть подписанной ( strong-named assembly ). Неподписанные
сборки идентифицируются своим именем, версией и информацией о типе культуры.
Подписанная сборка содержит также открытый ключ и цифровую подпись, созданную
закрытым ключом. Таким образом, при наличии открытого ключа можно проверить
неизменность кода сборки. Для генерации пары из закрытого и открытого ключа
служит утилита sn.exe из .NET Framework SDK. Для создания сборки из
приведенного выше файла SampleComponent.cs следует выполнить (или
внести в make файл) следующие комманды.
sn -k SampleComponent.snk
csc /target:library /r:System.EnterpriseServices.dll SampleComponent.cs
/keyfile:SampleComponent.snk
После успешного создания подписанной сборки остается зарегистрировать ее в каталоге COM+. В .NET Framework существуют три способа регистрации обслуживаемых компонент:
System.EnterpriseServices.RegistrationHelper ;regsvcs.exe ;Обычно рекомендуется применять первые два способа. Для регистрации созданной сборки выполняется следующая команда.
regsvcs SampleComponent.dll
Для удаления сборки из каталога COM+ выполняется аналогичная команда.
regsvcs /u SampleComponent.dll
В качестве примера использования созданной компоненты рассмотрим следующий файл.
// Файл SampleClient.cs
using System;
using ServicedComponentSample;
class Test
{
static public void Main()
{
using(SampleComponent com = new SampleComponent())
{
com.Do();
}
}
}
// SampleClient.cs
Использование оператора using приводит к вызову метода Dispose для объекта. Это необходимо для своевременной
очистки используемых при создании обслуживаемой компоненты ресурсов COM+.
Для компиляции данного файла следует следующую команду.
csc /target:exe /r:SampleComponent.dll SampleClient.cs
Для связывания этого приложения с компонентой на удаленном компьютере следует
зарегистрировать на нем сборку SampleComponent.dll. Затем, пользуясь
оснасткой comexp.msc на удаленном компьютере следует посредника
приложения COM+, и установить посредника на компьютере клиента с исполняемым
файлом SampleClient.exe. Следует отметить, что сборка может быть
либо установлена в каталог COM+ в качестве локального приложения, либо как
посредник приложения на удаленном компьютере, но не то и другое одновременно.
Использование исключений в распределенных системах имеет свои особенности. Если
исключение выбрасывается в обслуживаемой компоненте, то оно в общем случае
может выйти за границы домена приложения компоненты, то есть подвергнуться
сериализации и десериализации в рамках среды .BinaryFormatter. Поэтому все исключения, используемые
обслуживаемыми компонентами, должны иметь возможность сериализации
наследниками System.Runtime.Serialization.Formatter. Все исключения из
библиотеки FCL, наследованные от System.ApplicationException и
System.SystemException, имеют такую возможность. Ниже приведен пример создания
собственного сериализуемого исключения. Класс исключения имеет конструктор с
параметрами, описанными в теме об использовании форматеров SoapFormatter и BinaryFormatter. К счастью, разработчику
достаточно вызвать аналогичный конструктор базового класса System.SystemException, который реализует все необходимые действия.
// Файл ComException.cs
using System;
using System.EnterpriseServices;
using System.Runtime.Serialization;
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
[Serializable]
public class CustomException: System.ApplicationException
{
public CustomException(SerializationInfo info, StreamingContext context):
base(info, context)
{
}
public CustomException(string message): base(message)
{
}
}
public class ComSample: ServicedComponent
{
public ComSample()
{
}
public void Process()
{
throw new CustomException("случилась неприятность");
}
}
class MainApp
{
static public void Main()
{
ComSample com = new ComSample();
try
{
com.Process();
}
catch(CustomException e)
{
Console.WriteLine("Исключение: {0}", e.Message);
}
finally
{
com.Dispose();
}
}
} // Файл ComException.cs
Для создания приложения и его регистрации можно выполнить следующие команды.
sn -k ComException.snk csc ComException.cs /keyfile:ComException.snk /r:System.EnterpriseServices.dll regsvcs ComException.exe
Компенсирующий менеджер ресурсов позволяет использовать в транзакциях среды
COM+ какие либо ресурсы, которые не имеют прямой поддержки транзакций COM+.
Этот механизм включает следующие классы из пространства имен System.EnterpriseServices.CompensatingResourceManager:
log ), заполняемый операциями над ресурсом;Clerk ), ведущий журнал операций;Compensator ),
который восстанавливает первоначальное состояние ресурса в случае отката транзакции и
вносит изменения в ресурс при успехе транзакции.Сам менеджер ресурсов должен решить задачу изоляции ресурса в рамках транзакции и ведения журнала операций. При успехе транзакции компенсатор вносит постоянные изменения или отменяет их в соответствии с журналом операций. Следует отметить, что какие-либо временные изменения, производимые менеджером с ресурсом, могут вероятно нарушить требования изоляции транзакции, поскольку могут быть видимыми для внешних по отношению к транзакции объектов. С другой стороны, объекты внутри транзакции должны наблюдать происходящие с ресурсом изменения до успешного завершения транзакции.
В качестве примера менеджера ресурсов рассмотрим работу с файлами небольшого размера. Менеджер ресурсов предоставляет следующие сервисы:
С целью изоляции транзакций запись в файл происходит в момент успешного завершения транзакции. До этого момента данные хранятся в памяти. Такое решение безусловно не является эффективным с точки зрения траты ресурсов решение, оно используется в качестве примера. При чтении в рамках транзакции документа, уже ранее измененного в этой же транзакции, вместо чтения из файла происходит чтение последней относящийся к этому файлу записи из журнала операций. Журнал операций хранит пары из имени файла (в нижнем регистре) и содержимого файла.
Пример компилируется и запускается make файлом, который можно передать как
параметр входящей в состав .NET SDK утилите nmake.exe. Этот файл
имеет следующее содержание.
# Файл: makefile
all: CrmSample.exe
# сборка должна быть подписана
CrmSample.key:
sn -k CrmSample.key
CrmSample.exe: CrmSample.cs CrmSample.Key
csc /r:System.EnterpriseServices.dll CrmSample.cs
/keyfile:CrmSample.key
install:
# установить приложение COM+
regsvcs CrmSample.exe
uninstall:
regsvcs –u CrmSample.exe
Поскольку для регистрации сборки как приложения COM+необходимо, чтобы она была
подписана, то вызовом утилиты sn.exe из состава .NET csc.exe создает сборку,
используя этот файл ключей. При команде nmake install сборка
устанавливается в качестве приложения COM+ вызовом утилиты regsvcs.exe Microsoft Windows.
Далее будет рассмотрено содержание файла CrmSample.cs.
// Файл CrmSample.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationCrmEnabled]
[assembly: ApplicationName("Seva CRM")]
[assembly: Description("Пример на использование CRM")]
Данные атрибуты сборки из пространства имен System.EnterpriseServices
управляют параметрами создаваемого приложения COM+:
ApplicationActivation – задает тип приложения COM+ (серверный или библиотечный);ApplicationCrmEnabled – необходим для использования CRM в приложении COM+;ApplicationAccessControl – управляет контролем доступа к приложению,
в данном примере – отключен;System.ComponentModel.DescriptionAttribute задает описание сборки.Класс StreamLog содержит статический метод для записи в файл буфера,
вызываемый при завершении транзакции.
public static class StreamLog
{
public static void Save(LogRecord log)
{
if (log.Record is object[])
{
object[] record = (object[])log.Record;
string fileName = (string) record[0];
byte[] buffer = (byte[]) record[1];
using (FileStream file = new FileStream(fileName,
FileMode.Create, FileAccess.Write))
{
file.Write(buffer, 0, buffer.Length);
}
}
} // Save()
} // StreamLog
Класс StreamCache помогает организовать кеширование содержимого
файлов, использующихся в транзакции.
public class StreamCache
{
private string fileName;
private MemoryStream stream;
public MemoryStream Stream
{
get { return stream;}
}
public string FileName
{
get { return fileName;}
}
public StreamCache(string streamFileName)
{
fileName = streamFileName;
stream = new MemoryStream();
}
Метод Reopen при необходимости открывает повторно закрытый поток.
Поскольку такая операция не поддерживается классом MemoryStream,
то сначала в массив записывается все содержимое потока, затем создается новый поток,
куда записываются сохраненные данные.
public void Reopen()
{
if (!stream.CanRead)
{
stream.Close();
byte[] buffer = stream.ToArray();
stream.Dispose();
stream = new MemoryStream();
stream.Write(buffer, 0, buffer.Length);
stream.Seek(0, SeekOrigin.Begin);
}
} // Reopen()
При открытии файла для чтения вызывается метод Read,
считывающий все содержимое файла в поток типа MemoryStream.
public void Read()
{
byte[] buffer = new byte[32*1024];
using (Stream inputStream = new FileStream(fileName,
FileMode.Open, FileAccess.Read))
{
while (true)
{
int read = inputStream.Read(buffer, 0, buffer.Length);
if (read <= 0)
{
break;
}
Stream.Write(buffer, 0, read);
}
}
} // Read()
} // StreamCache
Класс StreamCrm является менеджером ресурсов, используемым
объектами COM+. Он является COM+ объектом, поэтому несколько участвующих в
транзакции компонент COM+ могут работать с одним менеджером ресурсов данного
типа. Менеджер содержит кеш для реализации отложенной до завершения транзакции
записи в файл.
public class StreamCrm: ServicedComponent
{
private Dictionary<string, StreamCache> cache;
public StreamCrm()
{
cache = new Dictionary<string, StreamCache>();
}
Метод StreamCrm.CheckCache проверяет, есть ли в кеше информация о
данном файле. При отсутствии ее в кеше в случае открытия файла на чтение
происходит считывания всего содержимого файла в кеш, в противном случае
связанный с файлом поток открывается повторно вызовом метода StreamCache.Reopen.
private StreamCache CheckCache(string fileName, bool read)
{
StreamCache streamCache;
string key = Path.GetFullPath(fileName).ToLower();
if (!cache.ContainsKey(key))
{
streamCache = new StreamCache(fileName);
cache.Add(key, streamCache);
if (read)
{
streamCache.Read();
};
}
else
{
streamCache = cache[key];
streamCache.Reopen();
};
return streamCache;
}
Метод StreamCrm.ReadFromFile вызывается клиентом, желающим читать
из файла, метод StreamCrm.WriteToFile – желающим перезаписать файл.
public MemoryStream ReadFromFile(string fileName)
{
return CheckCache(fileName, true).Stream;
}
public MemoryStream WriteToFile(String fileName)
{
return CheckCache(fileName, false).Stream;
}
Метод StreamCrm.Flush прекращает работу с потоком, открытым для
записи и сохраняет сделанные изменения в записи для журнала секретаря, которую
возвращает в качестве своего результата.
public object[] Flush(String fileName)
{
StreamCache streamCache = CheckCache(fileName, false);
streamCache.Stream.Close();
object[] record = new object[2];
record[0] = fileName;
record[1] = streamCache.Stream.ToArray();
return record;
}
Статический метод StreamCrm.CreateClerk создает секретаря для
ведения журнала операций над ресурсом. Вызов статического метода не приводит к
удаленному вызову и смене контекста.
public static Clerk CreateClerk()
{
return new Clerk(typeof(StreamCompensator), "Compensator",
CompensatorOptions.AllPhases);
}
}
Класс StreamCompensator наследуется от класса
и служит для сохранения результатов транзакции в случае успеха или возврате данных
в первоначальное состояние в случае отката транзакции.
public class StreamCompensator :
System.EnterpriseServices.CompensatingResourceManager.Compensator
{
private bool prepared = false;
Методы c суффиксом Prepare вызываются для проверки записей в
журнале перед завершением или откатом транзакции.
Метод PrepareRecord должен вернуть false,
если запись журнала должна быть использована. Поскольку журнал записей не контролирует
типы записей, это должен сделать метод PrepareRecord.
public override void BeginPrepare ()
{
}
public override bool PrepareRecord(LogRecord log)
{
prepared = false;
if (!(log.Record is object[]))
return true;
object[] record = log.Record as object[];
if (record.Length != 2)
return true;
if (!(record[0] is string) || !(record[1] is byte[]))
return true;
prepared = true;
return false;
}
Метод EndPrepare возвращает сохраненный в поле объекта
результат проверки записи. Если он возвращает true,
то возможно успешное завершение транзакции.
public override bool EndPrepare ()
{
return prepared;
}
Методы c суффиксом Commit вызываются для сохранения результата транзакции.
Если метод CommitRecord возвращает истинное значение,
то запись можно исключить из журнала операций.
public override void BeginCommit (bool commit)
{
}
public override bool CommitRecord (LogRecord log)
{
StreamLog.Save(log);
return true;
}
public override void EndCommit ()
{
}
Группа методов с суффиксом Abort служит
для возвращения ресурсу первоначального состояния при откате транзакции.
Поскольку созданный менеджер ресурсов изменяет состояние файлов меняется
только при успешном завершении транзакции, то эти методы не содержат никаких действий.
public override void BeginAbort (bool abort)
{
}
public override bool AbortRecord (LogRecord log)
{
return true;
}
public override void EndAbort ()
{
}
} // StreamCrm
Ниже приведен пример двух классов COM+, которые используют созданный менеджер
ресурсов. Напомним, что для регистрации в качестве COM+ компоненты классы
должен иметь публичный конструктор без параметров. Атрибут TransactionAttribute
управляет использованием транзакций COM+. Класс SampleCrmClient2
содержит метод ReadLine, читающий из файла строчку с использованием
созданного менеджера ресурсов.
[Transaction(TransactionOption.Required)]
public class SampleCrmClient2: ServicedComponent
{
public SampleCrmClient2()
{
}
public string ReadLine(StreamCrm crm, string fileName)
{
using (StreamReader reader = new StreamReader(crm.ReadFromFile(fileName)))
{
return line = reader.ReadLine();
}
}
}
Метод SampleCrmClient.DoSomeWork демонстрирует использование
CRM. Смена текущей директории вызвана тем, что по умолчанию для серверных
компонент COM+ текущей является директория %systemroot%\system32.
[Transaction(TransactionOption.RequiresNew)]
public class SampleCrmClient1: ServicedComponent
{
public SampleCrmClient1()
{
}
public void DoSomeWork(string dir)
{
const string fileName1 = "sample1.txt";
const string fileName2 = "sample2.txt";
Environment.CurrentDirectory = dir;
StreamCrm crm = new StreamCrm();
Clerk clerk = StreamCrm.CreateClerk();
Метод производит действия с двумя файлами. Сначала в поток, связанный с первым из
двух файлов, записывается некоторая строка. Собственно запись в файл в этот момент
не происходит, записанные данные запоминаются в кеше менеджера ресурсов после
вызова метода StreamCrm.Flush. Этот метод возвращает запись, которая
помещается в журнал секретаря и будет использована при завершении транзакции.
using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName1)))
{
writer.Write(Environment.CurrentDirectory);
}
// добавление в журнал записи
clerk.WriteLogRecord(crm.Flush(fileName1));
Затем из этого файла строка считывается другим объектом COM+ класса SampleCrmClient2. Таким образом, если объекты в пределах одной
транзакции будут обращаться к файлам при помощи одного экземпляра менеджера
ресурсов, то изменения, внесенные одним объектом, будут видны другим объектам
транзакции, но не видны снаружи до ее завершения.
String tempString = "";
using (SampleCrmClient2 client2 = new SampleCrmClient2())
{
tempString = client2.ReadLine(crm, Path.GetFullPath(fileName1));
}
Считанные объектом данные записываются во второй файл.
using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName2)))
{
writer.WriteLine(String.Format("Считано из файла [{0}]:", fileName1));
writer.WriteLine(tempString);
}
// добавление в журнал записи
clerk.WriteLogRecord(crm.Flush(fileName2));
// успешное завершение транзакциии, сохранение изменений в файлах
ContextUtil.SetComplete();
}
}
Класс CrmTest содержит метод Main. Вместо конструкции try .. finally … Dispose в C# следует использовать оператор using с тождественным результатом. Однако в данном примере
хотелось бы показать, что при использовании объектов среды EnterpriseServices/COM+
для них следует вызывать Dispose в явном или неявном (через оператор using ) виде.
class CrmTest
{
public static void Main()
{
SampleCrmClient1 client1 = new SampleCrmClient1();
try
{
client1.DoSomeWork(Environment.CurrentDirectory);
}
finally
{
client1.Dispose();
}
}
}
Кроме работы с компенсирующим менеджером ресурсов, на данном примере
хотелось бы показать, что хотя сборка мусора упрощает работу программиста,
но при использовании объектов с интерфейсом IDisposable всегда
следует вызывать их метод Dispose (явно или неявно) для
своевременного освобождения ресурсов.
Для использования слабо связанных событий необходимо (рис. 6.7):
(рис 6.7) Использование слабо связанных событийСначала создается интерфейс, описывающий один или несколько методов событий. Поскольку для подписки на событие необходимо будет использовать идентификаторы класса события и интерфейса, то они описаны как константы.
// LceEvents.cs
using System;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
public static class EventsGuids
{
public const string interfaceId = "A5105B2C-40BF-46C6-B19C-4286A423DBF9";
public const string eventClassId = "DF64D391-CCE9-4FC6-B5F2-3F4DE3FA48C2";
}
[Guid(EventsGuids.interfaceId)]
public interface ILceMessage
{
void TriggerEvent(string message);
}
// LceEvents.cs
Далее описано приложение, являющееся издателем события.
// LcePublisher.cs
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
[assembly: ApplicationName("LCE publisher")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
Необходимо описать класс события, используя атрибут EventClassAttribute
и описанный ранее уникальный идентификатор.
Методы данного класса не содержат кода, класс события нужен только
как формальная реализация интерфейса.
[EventClass]
[Guid(EventsGuids.eventClassId)]
[Transaction(TransactionOption.Disabled)]
public class LceEvents: ServicedComponent, ILceMessage
{
public void TriggerEvent(string message)
{
}
}
Издатель события может быть как обслуживаемой компонентой, так и не быть связанным с контекстом COM+. Следует учитывать, что при отсутствии у события подписчиков будет выброшено исключение COM+.
public class LcePublisher: ServicedComponent
{
public LcePublisher()
{
}
public void DoWork()
{
ILceMessage lcEvent = (ILceMessage) new LceEvents();
try
{
while (true)
{
lcEvent.TriggerEvent("Событие!");
Thread.Sleep(1000);
}
}
catch (System.Runtime.InteropServices.COMException)
{
Console.WriteLine("Нет подписчиков");
}
}
}
class MainApp
{
public static void Main()
{
LcePublisher com = new LcePublisher();
com.DoWork();
}
}
// LcePublisher.cs
Подписчик события является компонентой COM+, реализующей интерфейс события. Метод данного интерфейса вызывается при публикации события. В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего сообщает об успехе транзакции. Для простоты примера имя очереди указано как константа, реальные приложения должны хранить его в файле конфигурации.
// LceSubscriber.cs
using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Messaging;
using Seva.ComUtils;
using Seva.Msmq;
[assembly: ApplicationName("LCE demo")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
public static class Consts
{
public const string testQueue = @".\Private$\sample_queue";
}
[JustInTimeActivation]
[Transaction(TransactionOption.Required)]
public class LceSubscriber : ServicedComponent, ILceMessage
{
public LceSubscriber()
{
}
В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего информирует координатор транзакций об успехе транзакции.
public void TriggerEvent(string message)
{
MessageQueue queue = MsmqTools.CreateQueue(Consts.testQueue);
queue.Send(message, MessageQueueTransactionType.Automatic);
ContextUtil.SetComplete();
}
}
class MainApp
{
public static void Main()
{
// Для ленивой регистрации приложения создается
// служебная компонента
LceSubscriber subscriber = new LceSubscriber();
// Создание постоянной подписки
LceUtils.PermanentSubscription(Consts.comAppName,
typeof(LceSubscriber).ToString(),
EventsGuids.eventClassId, EventsGuids.interfaceId);
}
}
// LceSubscriber.cs
Make-файл для создания подписчика и издателя.
all: LceSubscriber.exe LcePublisher.exe
LceSubscriber.snk:
sn -k LceSubscriber.snk
LcePublisher.snk:
sn -k LcePublisher.snk
LceSubscriber.exe: Seva*.cs LceEvents.cs LceSubscriber.cs LceSubscriber.snk
csc /out:LceSubscriber.exe Seva*.cs LceEvents.cs LceSubscriber.cs
/r:interop.comadmin.dll /keyfile:LceSubscriber.snk
LcePublisher.exe: Seva*.cs LceEvents.cs LcePublisher.cs LcePublisher.snk
csc /out:LcePublisher.exe Seva*.cs LceEvents.cs LcePublisher.cs
/r:interop.comadmin.dll /keyfile:LcePublisher.snk
Сервисы без компонент (SWC) позволяют использовать сервисы среды COM+
без создания приложение COM+ и без наследования от класса ServicedComponent.
Для включения возможностей SWC следует включить сначала доступ по TCP/IP к
координатору распределенных транзакций. По умолчанию эта возможность отключена
в Windows 2003 и Windows XP SP2, поскольку уменьшает безопасность системы. Если
компьютер подключен к интернету через NAT, то ее включение достаточно безопасно.
Эту опцию можно включить через оснастку %systemroot%\system32\Com\comexp.msc
или импортом следующего файла в системный реестр:
REGEDIT4 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC\Security] "NetworkDtcAccess"=dword:00000001
Затем следует перезапустить службу координатора распределенных транзакций
net stop MSDTC net start MSDTC
Рассмотрим класс, облегчающий использование SWC. Поскольку использование COM+
связано с использованием неуправляемых ресурсов, класс реализует интерфейс System.IDisposable с единственным методом void Dispose(),
осуществляющим выход их домена COM+.
using System;
using System.EnterpriseServices;
namespace Seva.Swc
{
public class SwcUtil : IDisposable
{
public SwcUtil()
{
ServiceDomain.Enter(GetConfig(TransactionOption.Required));
}
public SwcUtil(TransactionOption transactionOption)
{
ServiceDomain.Enter(GetConfig(transactionOption));
}
protected virtual ServiceConfig GetConfig(
TransactionOption transactionOption)
{
ServiceConfig config = new ServiceConfig();
config.Transaction = transactionOption;
config.TrackingEnabled = true;
config.TrackingAppName = "SwcUtil";
config.TrackingComponentName = this.GetType().FullName;
return config;
}
public void Dispose()
{
ServiceDomain.Leave();
}
}
}
Пример использования данного класса приведен ниже. В качестве примера рассмотрена работа с очередями сообщений с использованием транзакций COM+.
using System;
using System.EnterpriseServices;
using System.Messaging;
using Seva.Msmq;
using Seva.Swc;
public class MainApp
{
public static void Main()
{
using (SwcUtil swc = new SwcUtil())
{
MessageQueue queue = MsmqTools.CreateQueue(@".\Private$\swc_queue");
queue.Send("SWC Message", MessageQueueTransactionType.Automatic);
Console.WriteLine("Нажмите <Enter> для завершения транзакции");
Console.ReadLine();
ContextUtil.SetComplete();
}
}
}
Использование SWC не требует подписанной сборки, поскольку сборка не регистрируется в качестве приложения COM+.
К сожалению, при использовании SWC невозможно использовать компенсирующий
менеджер ресурсов. Во первых, класс наследован от System.EnterpriseServices.ServicedComponent, то есть является
обслуживаемой компонентой, поэтому для его использования должно быть создано приложение COM+.
Поэтому при создании секретаря класса Clerk и регистрации компенсатора
при использовании сервисов без компонент происходит catastrophic exception,
если сборка подписана, иначе – жалоба на отсутствие подписи. Таким образом,
сервисы без компонент следует считать полумерой, благодаря которой можно работать
с ресурсами, поддерживающими распределенные транзакции, такими как MSMQ или MS SQL.
Хотя COM+ имеет обширные возможности для создания распределенных систем внутри предприятия, ей свойственны и следующие недостатки.
Рассмотрим промежуточную среду COM+ с точки зрения требований к распределенной системе.
Можно сделать вывод, что хотя промежуточная среда EnterpriseServices/COM+ предоставляет обширный набор сервисов для компонент распределенной системы, но ее использование ограничено взаимодействием компонент внутри локальной или виртуальной частной сети, построенной на базе Microsoft Windows в пределах одного предприятия.
COM+ – промежуточная среда для создания распределенных систем, действующих в локальной сети. Она разрабатывается фирмой Microsoft с конца 90-х годов и впервые появилась в составе операционной системы Microsoft Windows 2000. Основной целью разработки среды COM+ было создание инфраструктуры для разработки распределенных систем автоматизации предприятия. Основные достоинства среды COM+:
Среда COM+ управляет ходом выполнения объектов COM+, являющимися экземплярами так называемых компонент COM+. Набор связанных компонент COM+, находящихся в одной динамической библиотеке, называется приложением COM+. Приложение COM+ состоит из набора компонент и ролей для доступа к ним. Сведения о зарегистрированных приложениях хранятся в каталоге COM+.
Приложения COM+ бывают двух видов: библиотечные и серверные. Экземпляры компонент библиотечных приложений выполняются в том же процессе, что и использующий их клиент, компоненты серверного – в отдельном потоке сервера, возможно выполняющимся на удаленном компьютере. Только серверные приложения могут использоваться удаленно путем регистрации в каталоге COM+ на компьютере клиента посредника приложения COM+. После установки посредников использование удаленных серверных компонент COM+ не отличается от использования локальных серверных компонент. Понятие посредник ( proxy ) используется в COM+ в двух различных смыслах – запись в каталоге COM+, связанная с некоторым приложением COM+ на удаленном компьютере, и в том же смысле, что и посредник при удаленном вызове.
Поскольку библиотечные приложения COM+ не используют механизм удаленных вызовов и могут использоваться только в адресном пространстве клиента, то их рассмотрение не относится к данному курсу, и далее речь пойдет исключительно о серверных приложениях COM+.
На рис. 6.1 показана архитектура среды COM+ при использовании серверных приложений. Объекты COM+ являются экземплярами компонент COM+, зарегистрированных в каталоге. Заглушка на стороне серверного процесса называется в COM+ перехватчиком ( interceptor ).
(рис 6.1) Архитектура среды COM+Каталог COM+ каждого компьютера содержит список зарегистрированных на
компьютере локальных приложений COM+, а также список установленных
посредников для связи с приложениями удаленных компьютеров. Каталог устроен
иерархически, в виде дерева. Например, узел с описанием приложения COM+ содержит
узел со списком входящих в него компонент COM+ и узел со списком ролей
приложений. Управление каталогом COM+ происходит при помощи оснастки comexp.msc или программно, используя классы библиотеки comadmin.dll. Подробнее состав каталога описан в приложении I.
Поскольку среда COM+ реализует свое собственное управлением исполняемым внутри нее кодом, то существует понятие контекста COM+, который представляет собой окружение объекта COM+. Контекст закрепляется за объектом в момент его активации и сохраняется до его деактивации. При создании контекста учитываются атрибуты необходимости транзакции и синхронизации, установленные в активируемой компоненте COM+, а так же текущий контекст в момент активации объекта.
Целью создания промежуточной среды COM+ являлось предоставление компонентам COM+ набора сервисов, облегчающему создание надежной и масштабируемой распределенной системы. Начиная с Windows 2003/XP SP2, часть этих сервисов может быть доступна и без создания компонент COM+, путем использования так называемых сервисов без компонент.
Среда COM+ позволяет исключить проблемы с синхронизацией при обслуживании
запроса клиента путем так называемых активностей. Активность начинается в момент
создания клиентом COM+ объекта и заканчивается при его удалении. В течении
активности клиент вызывает методы компоненты COM+, в ходе которых она может
создавать другие объекты COM+, в том числе расположенные на удаленных
компьютерах, и вызывать их методы. При этом COM+ гарантирует, что в течение
одной активности в каждый момент выполняется метод только одного COM+ объекта
из всех участвующих в активности. Таким образом, активность представляет собой
некий логический
| Настройка синхронизации компоненты COM+ | Создатель объекта участвует в активности | Создатель объекта не участвует в активности |
|---|---|---|
| Не поддерживается (Not Supported) | Вне активности | Вне активности |
| Поддерживается (Supported) | Активность создателя | Вне активности |
| Требуется (Required) | Активность создателя | Новая активность |
| Требуется новая (Requires new) | Новая активность | Новая активность |
Следует учитывать, что настройки участия в транзакции компоненты влияют на ее настройки синхронизации, о чем будет сказано далее.
COM+ поддерживает динамическую балансировку нагрузки. Для этого необходимо создать кластер COM+ на базе серверной версии операционной системы Microsoft Windows (Windows 2000 Advanced Server и последующих). Кластер COM+ состоит из нескольких серверов и распределяющим между ними запросы маршрутизатором COM+. Для повышения надежность такой системы могут применяться способы быстрой замены маршрутизатора при его выходе из строя на базе Windows Clustering Services. Таким образом, среда COM+ позволяет при наличии необходимости достаточных финансовых ресурсов создать хорошо масштабируемую систему без уникальной точки сбоя.
Среда COM+ поддерживает два вида активации объектов – активация по требованию клиента и активация на время единственного вызова (называемая в среде COM+ JIT активацией) с возможностью использования пула объектов. Поскольку распределенные транзакции поддерживаются только для второго типа активации, в дальнейшем будет рассматриваться только он.
Одним из основных достоинств среды COM+ является поддержка распределенных транзакций на базе координатора распределенных транзакций. Настройки транзакции компоненты COM+ влияют на настройки синхронизации и активации. Любая включенная настройка транзакций, отличная от Not supported, требует использования JIT активации и ограничивает выбор настройки синхронизации не более чем вариантами Required и Requires new. При активации объекта среда COM+ определяет необходимость использования транзакций в соответствии с таблицей 6.2.
| Настройка транзакции компоненты COM+ | Создатель объекта участвует в транзакции | Создатель объекта не участвует в транзакции |
|---|---|---|
| Не поддерживается (Not Supported) | Вне транзакции | Вне транзакции |
| Поддерживается (Supported) | Транзакция создателя | Вне транзакции |
| Требуется (Required) | Транзакция создателя | Новая транзакция |
| Требуется новая (Requires new) | Новая транзакция | Новая транзакция |
Если при создании объекта обнаружена потребность в создании новой транзакции, среда COM+ создает ее с помощью координатора транзакций и данный объект считается корнем транзакции (рис. 6.2). Поскольку требующие транзакцию объекты используют активацию одного вызова, то транзакция не может существовать дольше, чем один вызов метода корневого объекта клиентом COM+. В транзакции могут участвовать службы, имеющие свой менеджер ресурсов, в частности MSMQ и MS SQL. Для многих ресурсов при необходимости можно создать свой компенсирующий менеджер ресурсов.
(рис 6.2) Транзакция COM+Каждый из участвующих в транзакции объектов должен в конце выполнения своего метода сообщить об успешности транзакции или ее неудаче. В случае, если все объекты объявили об успешности транзакции, служба COM+ оповещает все участвующие в транзакции внешние службы о необходимости сделать произведенные в рамках транзакции изменения постоянными (рис. 6.3).
(рис 6.3) Успешное завершение транзакции COM+
Хотя среда MSMQ может использоваться в рамках транзакции COM+, это приводит к одновременному использованию двух технологий удаленного взаимодействия. Вероятно часто было бы удобно скрывать использование MSMQ путем создания компонент COM+, поддерживающих асинхронные коммуникации. Поэтому для реализации асинхронного удаленного вызова в промежуточной среде COM+ существуют так называемые ожидающие компоненты ( queued componenets ), которые прозрачно используют MSMQ. Использование такой компоненты подобно асинхронному удаленному вызову (рис. 6.4).
(рис 6.4) Использование ожидающих компонент COM+Аналогичным образом происходит получение результата вызова удаленной компоненты: на сервере создается протоколист, а на клиенте – слушатель и исполнитель. Однако в этом случае до начала использования удаленной компоненты клиенту следует создать вызываемый объект ( call-back object ), который будет принимать ответ от сервера через исполнителя, и передать ссылку на такой объект (точнее, на его исполнителя), серверу (рис. 6.5).
(рис 6.5) Взаимодействие с отложенной компоненты
Среда COM+ позволяет компонентам подписаться на события, создаваемые какими либо объектами COM+ (издателями событий). При этом подписчики и издатели могут не знать о существовании друг друга, но должны знать о существовании общего интерфейса COM+, который используется для публикации событий. Наравне с отложенными компонентами, слабо связанные события предоставляют второй вариант реализации асинхронного обмена данными в среде COM+.
Определение политики управления доступом для компонент COM+ осуществляется на
основе
Прежде чем назначать роли группам, необходимо понять политику обеспечения безопасности приложения. В идеале названия ролей должны соответствовать категориям пользователей, которым они будут назначены. Кроме того, с помощью оснастки служб компонентов можно получить описание каждой роли, в котором могут содержаться сведения о том, какие пользователи могут назначаться на данную роль.
Среда COM+ была создана до технологии .NET, поэтому она работает с
неуправляемым кодом и не является носителем исполняемой среды CLR. Для
использовании сервисов COM+ из .NET Framework необходимо получить возможность
использовать управляемый код в контексте COM+. Для решения данной проблемы
была создана достаточно сложная схема взаимодействия сред CLR и COM+,
основанная на понятии компоненты, использующей сервисы COM+
( serviced component ), называемой далее обслуживаемой (средой
COM+) компонентой. Такая компонента состоит из объекта управляемого кода,
принадлежащему наследованному от System.EnterpriseServices.ServicedComponent классу.
Благодаря наследованию от ServicedComponent при исполнении методов этого
объекта имеется доступ к контексту COM+. Использование таких компонент
упрощенно показано на рис. 6.6.
(рис 6.6) Взаимодействие COM+ и CLR при использовании серверных приложенийМаршализация параметров методов обслуживаемой компоненты при использовании
происходит под управлением службы .BinaryFormatter ), а среда COM+ используется только для реализации
своих сервисов.
Как видно из рис. 6.6, обслуживаемая компонента
.NET Framework не является, строго говоря, компонентой COM+. Аналогично и
промежуточная среда .NET Enterpsise Services является не новым названием среды
COM+, а средством использования сервисов COM+ в управляемом коде. Хотя часто
можно упрощенно считать, что обслуживаемые компоненты – это компоненты COM+
на управляемом коде, но при разработке обслуживаемых компонент существуют
случаи, когда желательно понимать взаимосвязь CLR и COM+. Например, если при
вызове метода управляемой компоненты происходит ошибка в момент выполнения
неуправляемого кода среды COM+, то полученная от посредником COM+ информация
преобразуется в исключение типа System.Runtime.InteropServices.COMException.
В качестве текста сообщения в этом исключении фигурируют ошибки среды COM+, достаточно
малопонятные с точки зрения управляемого кода, например такие.
Unhandled Exception: System.Runtime.InteropServices.COMException (0x8000FFFF): Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)) Unhandled Exception: System.Runtime.InteropServices.COMException (0x8004D082): Exception from HRESULT: 0x8004D082
При возникновении исключения в управляемом коде обслуживаемой компоненты
происходит обычное исключение .NET, которое, тем не менее, должно уметь
маршализироваться по значению с помощью .
Обслуживаемая компонента .NET Framework является объектом класса,
наследованным от System.EnterpriseServices.ServicedComponent,
и отвечающим следующим требованиям:
public ;Статические методы в классе компоненты возможны, но они выполняются в контексте клиента, и имеют доступа к контексту COM+ клиента, если таковой существует.
Класс сервисной компоненты должен быть объявлен в сборке, которая может быть
зарегистрирована в качестве приложения COM+. Такая сборка не должна иметь классов
общего вида со спецификатором public, и должна быть подписана.
Класс обслуживаемой компоненты может иметь атрибуты из пространства имен System.EnterpriseServices, связанные с транзакцией
( TransactionAttribute ), активацией
( JustInTimeActivationAttribute и ObjectPoolingAttribute )
и синхронизацией ( SynchronizationAttribute ). Как упоминалось ранее,
поддержка транзакции автоматически означает JIT-активацию и участие в активности,
тем не менее следует перечислять атрибут JIT активации явно.
Рассмотрим код простейшей обслуживаемой компоненты.
// Файл SampleComponent.cs
using System;
using System.EnterpriseServices;
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationName("Serviced Component Example 01")]
[assembly: Description("Sample of .NET serviced component.")]
[assembly: ApplicationActivation(ActivationOption.Server)]
Эти атрибуты задают свойства сборки, которая затем будет зарегистрирована в
качестве приложения COM+. Атрибут ApplicationActivation
позволяет выбрать серверный или библиотечный тип приложения.
namespace ServicedComponentSample
{
[JustInTimeActivation]
[Transaction(TransactionOption.Required)]
[ObjectPooling(Enabled=true, MinPoolSize=5, MaxPoolSize=10)]
public class SampleComponent: ServicedComponent
{
Объявленный выше класс компоненты, как видно из атрибутов, требует наличия транзакции и использует JIT активацию с пулом объектов
public SampleComponent()
{
}
Для использования в пуле объект должен быть возвращен в свое первоначальное
состояние (деактивизирован). Для поддержки режима активации одного вызова
компонента не должна иметь состояния, сохраняемого между вызовами ее методов
клиентом, и таким образом часто она не требует каких-либо действий для перевода в
первоначальное состояние. При необходимости можно перекрыть виртуальные методы Activate и Deactivate, вызываемые при операциях
активации и деактивации, и производить в них сброс состояния объекта.
protected override void Activate()
{
}
protected override void Deactivate()
{
// Здесь при необходимости можно очистить
// состояние объекта и освободить ресурсы
}
Метод CanBePooled возвращает true,
если объект может быть возвращен в пул объектов после деактивации, иначе он уничтожается.
protected override bool CanBePooled()
{
// Подтверждает возможность помещения объекта в пул
return true;
}
Атрибут System.EnterpriseServices.AutoCompleteAttribute применяется
для автоматического участия методов в транзакции. Если метод завершается
нормально, то считается, что он подтвердил успешное завершение транзакции, при
возникновении же в методе не пойманного исключения транзакция будет отменена.
[AutoComplete]
public void Do()
{
}
}
}
// Файл SampleComponent.cs
Альтернативный AutoComplete вариант использования транзакций
заключается в вызове статических методов класса ContextUtil. Этот
класс используется для доступа и изменения контекста COM+, связанного с
обслуживаемой компонентой. Метод ContextUtil.SetComplete сообщает
COM+ об успешном завершении метода, а ContextUtil.SetAbort
предопределяет отмену транзакции. Используются они обычно следующим образом.
try
{
...
ContextUtil.SetComplete();
}
catch()
{
ContextUtil.SetAbort();
}
Постоянное использование методов ContextUtil приводит к коду,
который будет сложно перенести из компоненты COM+ в класс, выполняемый вне
промежуточной среды COM+. Для решения этой проблемы следует либо использовать
методы класса, оборачивающего методы ContextUtil, либо, в крайнем
случае, атрибут AutoComplete, который игнорируется для обычных классов.
Сборка, содержащая описание одного или нескольких классов компонент, должна быть
зарегистрирована как приложение COM+ в каталоге COM+. Для этого сборка должна
быть подписанной ( strong-named assembly ). Неподписанные
сборки идентифицируются своим именем, версией и информацией о типе культуры.
Подписанная сборка содержит также открытый ключ и цифровую подпись, созданную
закрытым ключом. Таким образом, при наличии открытого ключа можно проверить
неизменность кода сборки. Для генерации пары из закрытого и открытого ключа
служит утилита sn.exe из .NET Framework SDK. Для создания сборки из
приведенного выше файла SampleComponent.cs следует выполнить (или
внести в make файл) следующие комманды.
sn -k SampleComponent.snk
csc /target:library /r:System.EnterpriseServices.dll SampleComponent.cs
/keyfile:SampleComponent.snk
После успешного создания подписанной сборки остается зарегистрировать ее в каталоге COM+. В .NET Framework существуют три способа регистрации обслуживаемых компонент:
System.EnterpriseServices.RegistrationHelper ;regsvcs.exe ;Обычно рекомендуется применять первые два способа. Для регистрации созданной сборки выполняется следующая команда.
regsvcs SampleComponent.dll
Для удаления сборки из каталога COM+ выполняется аналогичная команда.
regsvcs /u SampleComponent.dll
В качестве примера использования созданной компоненты рассмотрим следующий файл.
// Файл SampleClient.cs
using System;
using ServicedComponentSample;
class Test
{
static public void Main()
{
using(SampleComponent com = new SampleComponent())
{
com.Do();
}
}
}
// SampleClient.cs
Использование оператора using приводит к вызову метода Dispose для объекта. Это необходимо для своевременной
очистки используемых при создании обслуживаемой компоненты ресурсов COM+.
Для компиляции данного файла следует следующую команду.
csc /target:exe /r:SampleComponent.dll SampleClient.cs
Для связывания этого приложения с компонентой на удаленном компьютере следует
зарегистрировать на нем сборку SampleComponent.dll. Затем, пользуясь
оснасткой comexp.msc на удаленном компьютере следует посредника
приложения COM+, и установить посредника на компьютере клиента с исполняемым
файлом SampleClient.exe. Следует отметить, что сборка может быть
либо установлена в каталог COM+ в качестве локального приложения, либо как
посредник приложения на удаленном компьютере, но не то и другое одновременно.
Использование исключений в распределенных системах имеет свои особенности. Если
исключение выбрасывается в обслуживаемой компоненте, то оно в общем случае
может выйти за границы домена приложения компоненты, то есть подвергнуться
сериализации и десериализации в рамках среды .BinaryFormatter. Поэтому все исключения, используемые
обслуживаемыми компонентами, должны иметь возможность сериализации
наследниками System.Runtime.Serialization.Formatter. Все исключения из
библиотеки FCL, наследованные от System.ApplicationException и
System.SystemException, имеют такую возможность. Ниже приведен пример создания
собственного сериализуемого исключения. Класс исключения имеет конструктор с
параметрами, описанными в теме об использовании форматеров SoapFormatter и BinaryFormatter. К счастью, разработчику
достаточно вызвать аналогичный конструктор базового класса System.SystemException, который реализует все необходимые действия.
// Файл ComException.cs
using System;
using System.EnterpriseServices;
using System.Runtime.Serialization;
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
[Serializable]
public class CustomException: System.ApplicationException
{
public CustomException(SerializationInfo info, StreamingContext context):
base(info, context)
{
}
public CustomException(string message): base(message)
{
}
}
public class ComSample: ServicedComponent
{
public ComSample()
{
}
public void Process()
{
throw new CustomException("случилась неприятность");
}
}
class MainApp
{
static public void Main()
{
ComSample com = new ComSample();
try
{
com.Process();
}
catch(CustomException e)
{
Console.WriteLine("Исключение: {0}", e.Message);
}
finally
{
com.Dispose();
}
}
} // Файл ComException.cs
Для создания приложения и его регистрации можно выполнить следующие команды.
sn -k ComException.snk csc ComException.cs /keyfile:ComException.snk /r:System.EnterpriseServices.dll regsvcs ComException.exe
Компенсирующий менеджер ресурсов позволяет использовать в транзакциях среды
COM+ какие либо ресурсы, которые не имеют прямой поддержки транзакций COM+.
Этот механизм включает следующие классы из пространства имен System.EnterpriseServices.CompensatingResourceManager:
log ), заполняемый операциями над ресурсом;Clerk ), ведущий журнал операций;Compensator ),
который восстанавливает первоначальное состояние ресурса в случае отката транзакции и
вносит изменения в ресурс при успехе транзакции.Сам менеджер ресурсов должен решить задачу изоляции ресурса в рамках транзакции и ведения журнала операций. При успехе транзакции компенсатор вносит постоянные изменения или отменяет их в соответствии с журналом операций. Следует отметить, что какие-либо временные изменения, производимые менеджером с ресурсом, могут вероятно нарушить требования изоляции транзакции, поскольку могут быть видимыми для внешних по отношению к транзакции объектов. С другой стороны, объекты внутри транзакции должны наблюдать происходящие с ресурсом изменения до успешного завершения транзакции.
В качестве примера менеджера ресурсов рассмотрим работу с файлами небольшого размера. Менеджер ресурсов предоставляет следующие сервисы:
С целью изоляции транзакций запись в файл происходит в момент успешного завершения транзакции. До этого момента данные хранятся в памяти. Такое решение безусловно не является эффективным с точки зрения траты ресурсов решение, оно используется в качестве примера. При чтении в рамках транзакции документа, уже ранее измененного в этой же транзакции, вместо чтения из файла происходит чтение последней относящийся к этому файлу записи из журнала операций. Журнал операций хранит пары из имени файла (в нижнем регистре) и содержимого файла.
Пример компилируется и запускается make файлом, который можно передать как
параметр входящей в состав .NET SDK утилите nmake.exe. Этот файл
имеет следующее содержание.
# Файл: makefile
all: CrmSample.exe
# сборка должна быть подписана
CrmSample.key:
sn -k CrmSample.key
CrmSample.exe: CrmSample.cs CrmSample.Key
csc /r:System.EnterpriseServices.dll CrmSample.cs
/keyfile:CrmSample.key
install:
# установить приложение COM+
regsvcs CrmSample.exe
uninstall:
regsvcs –u CrmSample.exe
Поскольку для регистрации сборки как приложения COM+необходимо, чтобы она была
подписана, то вызовом утилиты sn.exe из состава .NET csc.exe создает сборку,
используя этот файл ключей. При команде nmake install сборка
устанавливается в качестве приложения COM+ вызовом утилиты regsvcs.exe Microsoft Windows.
Далее будет рассмотрено содержание файла CrmSample.cs.
// Файл CrmSample.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationCrmEnabled]
[assembly: ApplicationName("Seva CRM")]
[assembly: Description("Пример на использование CRM")]
Данные атрибуты сборки из пространства имен System.EnterpriseServices
управляют параметрами создаваемого приложения COM+:
ApplicationActivation – задает тип приложения COM+ (серверный или библиотечный);ApplicationCrmEnabled – необходим для использования CRM в приложении COM+;ApplicationAccessControl – управляет контролем доступа к приложению,
в данном примере – отключен;System.ComponentModel.DescriptionAttribute задает описание сборки.Класс StreamLog содержит статический метод для записи в файл буфера,
вызываемый при завершении транзакции.
public static class StreamLog
{
public static void Save(LogRecord log)
{
if (log.Record is object[])
{
object[] record = (object[])log.Record;
string fileName = (string) record[0];
byte[] buffer = (byte[]) record[1];
using (FileStream file = new FileStream(fileName,
FileMode.Create, FileAccess.Write))
{
file.Write(buffer, 0, buffer.Length);
}
}
} // Save()
} // StreamLog
Класс StreamCache помогает организовать кеширование содержимого
файлов, использующихся в транзакции.
public class StreamCache
{
private string fileName;
private MemoryStream stream;
public MemoryStream Stream
{
get { return stream;}
}
public string FileName
{
get { return fileName;}
}
public StreamCache(string streamFileName)
{
fileName = streamFileName;
stream = new MemoryStream();
}
Метод Reopen при необходимости открывает повторно закрытый поток.
Поскольку такая операция не поддерживается классом MemoryStream,
то сначала в массив записывается все содержимое потока, затем создается новый поток,
куда записываются сохраненные данные.
public void Reopen()
{
if (!stream.CanRead)
{
stream.Close();
byte[] buffer = stream.ToArray();
stream.Dispose();
stream = new MemoryStream();
stream.Write(buffer, 0, buffer.Length);
stream.Seek(0, SeekOrigin.Begin);
}
} // Reopen()
При открытии файла для чтения вызывается метод Read,
считывающий все содержимое файла в поток типа MemoryStream.
public void Read()
{
byte[] buffer = new byte[32*1024];
using (Stream inputStream = new FileStream(fileName,
FileMode.Open, FileAccess.Read))
{
while (true)
{
int read = inputStream.Read(buffer, 0, buffer.Length);
if (read <= 0)
{
break;
}
Stream.Write(buffer, 0, read);
}
}
} // Read()
} // StreamCache
Класс StreamCrm является менеджером ресурсов, используемым
объектами COM+. Он является COM+ объектом, поэтому несколько участвующих в
транзакции компонент COM+ могут работать с одним менеджером ресурсов данного
типа. Менеджер содержит кеш для реализации отложенной до завершения транзакции
записи в файл.
public class StreamCrm: ServicedComponent
{
private Dictionary<string, StreamCache> cache;
public StreamCrm()
{
cache = new Dictionary<string, StreamCache>();
}
Метод StreamCrm.CheckCache проверяет, есть ли в кеше информация о
данном файле. При отсутствии ее в кеше в случае открытия файла на чтение
происходит считывания всего содержимого файла в кеш, в противном случае
связанный с файлом поток открывается повторно вызовом метода StreamCache.Reopen.
private StreamCache CheckCache(string fileName, bool read)
{
StreamCache streamCache;
string key = Path.GetFullPath(fileName).ToLower();
if (!cache.ContainsKey(key))
{
streamCache = new StreamCache(fileName);
cache.Add(key, streamCache);
if (read)
{
streamCache.Read();
};
}
else
{
streamCache = cache[key];
streamCache.Reopen();
};
return streamCache;
}
Метод StreamCrm.ReadFromFile вызывается клиентом, желающим читать
из файла, метод StreamCrm.WriteToFile – желающим перезаписать файл.
public MemoryStream ReadFromFile(string fileName)
{
return CheckCache(fileName, true).Stream;
}
public MemoryStream WriteToFile(String fileName)
{
return CheckCache(fileName, false).Stream;
}
Метод StreamCrm.Flush прекращает работу с потоком, открытым для
записи и сохраняет сделанные изменения в записи для журнала секретаря, которую
возвращает в качестве своего результата.
public object[] Flush(String fileName)
{
StreamCache streamCache = CheckCache(fileName, false);
streamCache.Stream.Close();
object[] record = new object[2];
record[0] = fileName;
record[1] = streamCache.Stream.ToArray();
return record;
}
Статический метод StreamCrm.CreateClerk создает секретаря для
ведения журнала операций над ресурсом. Вызов статического метода не приводит к
удаленному вызову и смене контекста.
public static Clerk CreateClerk()
{
return new Clerk(typeof(StreamCompensator), "Compensator",
CompensatorOptions.AllPhases);
}
}
Класс StreamCompensator наследуется от класса
и служит для сохранения результатов транзакции в случае успеха или возврате данных
в первоначальное состояние в случае отката транзакции.
public class StreamCompensator :
System.EnterpriseServices.CompensatingResourceManager.Compensator
{
private bool prepared = false;
Методы c суффиксом Prepare вызываются для проверки записей в
журнале перед завершением или откатом транзакции.
Метод PrepareRecord должен вернуть false,
если запись журнала должна быть использована. Поскольку журнал записей не контролирует
типы записей, это должен сделать метод PrepareRecord.
public override void BeginPrepare ()
{
}
public override bool PrepareRecord(LogRecord log)
{
prepared = false;
if (!(log.Record is object[]))
return true;
object[] record = log.Record as object[];
if (record.Length != 2)
return true;
if (!(record[0] is string) || !(record[1] is byte[]))
return true;
prepared = true;
return false;
}
Метод EndPrepare возвращает сохраненный в поле объекта
результат проверки записи. Если он возвращает true,
то возможно успешное завершение транзакции.
public override bool EndPrepare ()
{
return prepared;
}
Методы c суффиксом Commit вызываются для сохранения результата транзакции.
Если метод CommitRecord возвращает истинное значение,
то запись можно исключить из журнала операций.
public override void BeginCommit (bool commit)
{
}
public override bool CommitRecord (LogRecord log)
{
StreamLog.Save(log);
return true;
}
public override void EndCommit ()
{
}
Группа методов с суффиксом Abort служит
для возвращения ресурсу первоначального состояния при откате транзакции.
Поскольку созданный менеджер ресурсов изменяет состояние файлов меняется
только при успешном завершении транзакции, то эти методы не содержат никаких действий.
public override void BeginAbort (bool abort)
{
}
public override bool AbortRecord (LogRecord log)
{
return true;
}
public override void EndAbort ()
{
}
} // StreamCrm
Ниже приведен пример двух классов COM+, которые используют созданный менеджер
ресурсов. Напомним, что для регистрации в качестве COM+ компоненты классы
должен иметь публичный конструктор без параметров. Атрибут TransactionAttribute
управляет использованием транзакций COM+. Класс SampleCrmClient2
содержит метод ReadLine, читающий из файла строчку с использованием
созданного менеджера ресурсов.
[Transaction(TransactionOption.Required)]
public class SampleCrmClient2: ServicedComponent
{
public SampleCrmClient2()
{
}
public string ReadLine(StreamCrm crm, string fileName)
{
using (StreamReader reader = new StreamReader(crm.ReadFromFile(fileName)))
{
return line = reader.ReadLine();
}
}
}
Метод SampleCrmClient.DoSomeWork демонстрирует использование
CRM. Смена текущей директории вызвана тем, что по умолчанию для серверных
компонент COM+ текущей является директория %systemroot%\system32.
[Transaction(TransactionOption.RequiresNew)]
public class SampleCrmClient1: ServicedComponent
{
public SampleCrmClient1()
{
}
public void DoSomeWork(string dir)
{
const string fileName1 = "sample1.txt";
const string fileName2 = "sample2.txt";
Environment.CurrentDirectory = dir;
StreamCrm crm = new StreamCrm();
Clerk clerk = StreamCrm.CreateClerk();
Метод производит действия с двумя файлами. Сначала в поток, связанный с первым из
двух файлов, записывается некоторая строка. Собственно запись в файл в этот момент
не происходит, записанные данные запоминаются в кеше менеджера ресурсов после
вызова метода StreamCrm.Flush. Этот метод возвращает запись, которая
помещается в журнал секретаря и будет использована при завершении транзакции.
using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName1)))
{
writer.Write(Environment.CurrentDirectory);
}
// добавление в журнал записи
clerk.WriteLogRecord(crm.Flush(fileName1));
Затем из этого файла строка считывается другим объектом COM+ класса SampleCrmClient2. Таким образом, если объекты в пределах одной
транзакции будут обращаться к файлам при помощи одного экземпляра менеджера
ресурсов, то изменения, внесенные одним объектом, будут видны другим объектам
транзакции, но не видны снаружи до ее завершения.
String tempString = "";
using (SampleCrmClient2 client2 = new SampleCrmClient2())
{
tempString = client2.ReadLine(crm, Path.GetFullPath(fileName1));
}
Считанные объектом данные записываются во второй файл.
using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName2)))
{
writer.WriteLine(String.Format("Считано из файла [{0}]:", fileName1));
writer.WriteLine(tempString);
}
// добавление в журнал записи
clerk.WriteLogRecord(crm.Flush(fileName2));
// успешное завершение транзакциии, сохранение изменений в файлах
ContextUtil.SetComplete();
}
}
Класс CrmTest содержит метод Main. Вместо конструкции try .. finally … Dispose в C# следует использовать оператор using с тождественным результатом. Однако в данном примере
хотелось бы показать, что при использовании объектов среды EnterpriseServices/COM+
для них следует вызывать Dispose в явном или неявном (через оператор using ) виде.
class CrmTest
{
public static void Main()
{
SampleCrmClient1 client1 = new SampleCrmClient1();
try
{
client1.DoSomeWork(Environment.CurrentDirectory);
}
finally
{
client1.Dispose();
}
}
}
Кроме работы с компенсирующим менеджером ресурсов, на данном примере
хотелось бы показать, что хотя сборка мусора упрощает работу программиста,
но при использовании объектов с интерфейсом IDisposable всегда
следует вызывать их метод Dispose (явно или неявно) для
своевременного освобождения ресурсов.
Для использования слабо связанных событий необходимо (рис. 6.7):
(рис 6.7) Использование слабо связанных событийСначала создается интерфейс, описывающий один или несколько методов событий. Поскольку для подписки на событие необходимо будет использовать идентификаторы класса события и интерфейса, то они описаны как константы.
// LceEvents.cs
using System;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
public static class EventsGuids
{
public const string interfaceId = "A5105B2C-40BF-46C6-B19C-4286A423DBF9";
public const string eventClassId = "DF64D391-CCE9-4FC6-B5F2-3F4DE3FA48C2";
}
[Guid(EventsGuids.interfaceId)]
public interface ILceMessage
{
void TriggerEvent(string message);
}
// LceEvents.cs
Далее описано приложение, являющееся издателем события.
// LcePublisher.cs
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
[assembly: ApplicationName("LCE publisher")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
Необходимо описать класс события, используя атрибут EventClassAttribute
и описанный ранее уникальный идентификатор.
Методы данного класса не содержат кода, класс события нужен только
как формальная реализация интерфейса.
[EventClass]
[Guid(EventsGuids.eventClassId)]
[Transaction(TransactionOption.Disabled)]
public class LceEvents: ServicedComponent, ILceMessage
{
public void TriggerEvent(string message)
{
}
}
Издатель события может быть как обслуживаемой компонентой, так и не быть связанным с контекстом COM+. Следует учитывать, что при отсутствии у события подписчиков будет выброшено исключение COM+.
public class LcePublisher: ServicedComponent
{
public LcePublisher()
{
}
public void DoWork()
{
ILceMessage lcEvent = (ILceMessage) new LceEvents();
try
{
while (true)
{
lcEvent.TriggerEvent("Событие!");
Thread.Sleep(1000);
}
}
catch (System.Runtime.InteropServices.COMException)
{
Console.WriteLine("Нет подписчиков");
}
}
}
class MainApp
{
public static void Main()
{
LcePublisher com = new LcePublisher();
com.DoWork();
}
}
// LcePublisher.cs
Подписчик события является компонентой COM+, реализующей интерфейс события. Метод данного интерфейса вызывается при публикации события. В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего сообщает об успехе транзакции. Для простоты примера имя очереди указано как константа, реальные приложения должны хранить его в файле конфигурации.
// LceSubscriber.cs
using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Messaging;
using Seva.ComUtils;
using Seva.Msmq;
[assembly: ApplicationName("LCE demo")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
public static class Consts
{
public const string testQueue = @".\Private$\sample_queue";
}
[JustInTimeActivation]
[Transaction(TransactionOption.Required)]
public class LceSubscriber : ServicedComponent, ILceMessage
{
public LceSubscriber()
{
}
В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего информирует координатор транзакций об успехе транзакции.
public void TriggerEvent(string message)
{
MessageQueue queue = MsmqTools.CreateQueue(Consts.testQueue);
queue.Send(message, MessageQueueTransactionType.Automatic);
ContextUtil.SetComplete();
}
}
class MainApp
{
public static void Main()
{
// Для ленивой регистрации приложения создается
// служебная компонента
LceSubscriber subscriber = new LceSubscriber();
// Создание постоянной подписки
LceUtils.PermanentSubscription(Consts.comAppName,
typeof(LceSubscriber).ToString(),
EventsGuids.eventClassId, EventsGuids.interfaceId);
}
}
// LceSubscriber.cs
Make-файл для создания подписчика и издателя.
all: LceSubscriber.exe LcePublisher.exe
LceSubscriber.snk:
sn -k LceSubscriber.snk
LcePublisher.snk:
sn -k LcePublisher.snk
LceSubscriber.exe: Seva*.cs LceEvents.cs LceSubscriber.cs LceSubscriber.snk
csc /out:LceSubscriber.exe Seva*.cs LceEvents.cs LceSubscriber.cs
/r:interop.comadmin.dll /keyfile:LceSubscriber.snk
LcePublisher.exe: Seva*.cs LceEvents.cs LcePublisher.cs LcePublisher.snk
csc /out:LcePublisher.exe Seva*.cs LceEvents.cs LcePublisher.cs
/r:interop.comadmin.dll /keyfile:LcePublisher.snk
Сервисы без компонент (SWC) позволяют использовать сервисы среды COM+
без создания приложение COM+ и без наследования от класса ServicedComponent.
Для включения возможностей SWC следует включить сначала доступ по TCP/IP к
координатору распределенных транзакций. По умолчанию эта возможность отключена
в Windows 2003 и Windows XP SP2, поскольку уменьшает безопасность системы. Если
компьютер подключен к интернету через NAT, то ее включение достаточно безопасно.
Эту опцию можно включить через оснастку %systemroot%\system32\Com\comexp.msc
или импортом следующего файла в системный реестр:
REGEDIT4 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC\Security] "NetworkDtcAccess"=dword:00000001
Затем следует перезапустить службу координатора распределенных транзакций
net stop MSDTC net start MSDTC
Рассмотрим класс, облегчающий использование SWC. Поскольку использование COM+
связано с использованием неуправляемых ресурсов, класс реализует интерфейс System.IDisposable с единственным методом void Dispose(),
осуществляющим выход их домена COM+.
using System;
using System.EnterpriseServices;
namespace Seva.Swc
{
public class SwcUtil : IDisposable
{
public SwcUtil()
{
ServiceDomain.Enter(GetConfig(TransactionOption.Required));
}
public SwcUtil(TransactionOption transactionOption)
{
ServiceDomain.Enter(GetConfig(transactionOption));
}
protected virtual ServiceConfig GetConfig(
TransactionOption transactionOption)
{
ServiceConfig config = new ServiceConfig();
config.Transaction = transactionOption;
config.TrackingEnabled = true;
config.TrackingAppName = "SwcUtil";
config.TrackingComponentName = this.GetType().FullName;
return config;
}
public void Dispose()
{
ServiceDomain.Leave();
}
}
}
Пример использования данного класса приведен ниже. В качестве примера рассмотрена работа с очередями сообщений с использованием транзакций COM+.
using System;
using System.EnterpriseServices;
using System.Messaging;
using Seva.Msmq;
using Seva.Swc;
public class MainApp
{
public static void Main()
{
using (SwcUtil swc = new SwcUtil())
{
MessageQueue queue = MsmqTools.CreateQueue(@".\Private$\swc_queue");
queue.Send("SWC Message", MessageQueueTransactionType.Automatic);
Console.WriteLine("Нажмите <Enter> для завершения транзакции");
Console.ReadLine();
ContextUtil.SetComplete();
}
}
}
Использование SWC не требует подписанной сборки, поскольку сборка не регистрируется в качестве приложения COM+.
К сожалению, при использовании SWC невозможно использовать компенсирующий
менеджер ресурсов. Во первых, класс наследован от System.EnterpriseServices.ServicedComponent, то есть является
обслуживаемой компонентой, поэтому для его использования должно быть создано приложение COM+.
Поэтому при создании секретаря класса Clerk и регистрации компенсатора
при использовании сервисов без компонент происходит catastrophic exception,
если сборка подписана, иначе – жалоба на отсутствие подписи. Таким образом,
сервисы без компонент следует считать полумерой, благодаря которой можно работать
с ресурсами, поддерживающими распределенные транзакции, такими как MSMQ или MS SQL.
Хотя COM+ имеет обширные возможности для создания распределенных систем внутри предприятия, ей свойственны и следующие недостатки.
Рассмотрим промежуточную среду COM+ с точки зрения требований к распределенной системе.
Можно сделать вывод, что хотя промежуточная среда EnterpriseServices/COM+ предоставляет обширный набор сервисов для компонент распределенной системы, но ее использование ограничено взаимодействием компонент внутри локальной или виртуальной частной сети, построенной на базе Microsoft Windows в пределах одного предприятия.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.