
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Внесение косметических изменений в серверную часть
Так как до этого наш код плагины не поддерживал, наш сервер функционировал немного по-другому. Предполагалось, что сервер сам должен закрываться после того, как от него отключается последний клиент. Клиентов я считал по количеству созданных экземпляров COM-объектов. Тогда, когда счётчик объектов становился равен одному (главная форма сервера тоже хранит ссылку на ZigzagControl), сервер выгружался. Теперь же каждый плагин хранит по одному ZigzagControl-у. Учтём это на сервере. Откроем файл ComServer.cs и изменим его. Сперва добавим туда счётчик плагинов и методы его изменения.
public static int PluginsCount
{ get; set; }
public static void IncreasePluginsCount()
{
PluginsCount++;
}
public static void DecreasePluginsCount()
{
PluginsCount--;
}
Теперь немного перепишем метод Unlock, изменим условие выгрузки сервера из рассчёта на то, что каждый плагин ссылается на один ZigzagControl и один экземпляр Zigzag хранится на сервере в том случае, если где-то помимо сервера он нужен. То есть, примерно так:
Каждый клиент содержит один ZigzagControl
Каждый плагин содержит один ZigzagControl
Сервер содержит один ZigzagControl
Если вне сервера содержится хотя бы один ZigzagControl, то на сервере содержится ещё и экземпляр класса Zigzag.
Таким образом, заменяем метод Unlock на:
/// <summary>
/// Уменьшает счётчик ссылок на единицу.
/// Если счётчик ссылок принимает значение, равное нулю, генерирует событие OnComReleased. Это говорит о том, что все экземпляры COM-классов, выданные COM-клиентам, больше им не нужны и можно безопасно закрывать сервер.
/// </summary>
public static void Unlock()
{
uint refCount = CoReleaseServerProcess();
if ((refCount == PluginsCount + 1 + ((PluginsCount == 0) ? 0 : 1)))
if (OnComReleased != null)
OnComReleased();
}
А ещё я заметил, что для одного из запланированных плагинов мне не хватает функционала интерфейса IZigzagControl. Придётся туда добавить ещё один метод. Если бы дело было уже после гипотетического выхода нашего ZigzagServer-а на рынок, то изменять интерфейсы вот таким вот варварским путём не следовало. Кошерным считается добавлять новый интерфейс, наследующийся от старого и содержащий новые необходимые функции. То есть обычно создают IZigzagControl2 с новыми возможностями, а все старые сохраняются из-за наследования. Таким образом, старые клиенты смогут использовать старую версию интерфейса и не сломаются, а новые клиенты будут использовать новый интерфейс. Итак, добавим в IZigzagControl новый метод.
Если ваш интерфейс имеет тип IDispatch, то лучше добавлять методы в конец.
/// <summary>
/// Задаёт массив точек ломаной.
/// </summary>
/// <param name="points">Массив точек. Массив экземпляров IPair, левое значение которой - int, количество мс от момента создания сервера до момента создания точки, правое значение - IPoint, координаты точки.</param>
void SetPoints(IPair[] points);
Реализуем этот метод в ZigzagControl.cs, добавим его в регион членов IZigzagControl.
/// <summary>
/// Задаёт массив точек к ломаной.
/// </summary>
/// <param name="points">Массив точек. Массив экземпляров IPair, левое значение которой - int, количество мс от момента создания сервера до момента создания точки, правое значение - IPoint, координаты точки</param>
public void SetPoints(IPair[] points)
{
zigzag.Lock();
try
{
zigzag.Clear();
zigzag.Add(points);
}
catch (Exception exc)
{
ErrorReporter.ShowExceptionMessage(exc);
}
finally
{
zigzag.ReleaseLock();
}
}
Тут тоже всё просто, блокируем объект зигзаг, чтобы никто не получил к нему доступ, пока мы совершаем свои махинации, очищаем его, заполняем его новыми точками, снимаем блокировку.
Пришла в голову идея. Из-за того, что события генерирует класс Zigzag, а не ZigzagControl, у нас события изменения списка точек будут генерироваться некоторое лишнее количество раз. Возможно, в следующей редакции я от этого избавлюсь, но это повод для вас так не делать. Если хотите немного углубиться – исправьте эту мою недоработку.
Кроме того, необходимо несколько изменить код класса Zigzag.cs, а именно всю ветку кода с условимем “if (element is IList)” метода Add. Всё это условие заменяется на следующий код:
if ((element is IList) || (element is IPair[]))
{
LocalLock();
//Попробуем перенести данные из переданного аргумента в нативные фреймворковские списки.
//Если структура списка была некорректной, то возникнет исключение и ни одна из точек списка добавлена в ломаную не будет.
try
{
IPair[] pointsArray;
if (element is IList)
{
IList list = (IList)element;
List<IPair> tempList = new List<IPair>(list.Count);
for (int i = 0; i < list.Count; i++)
tempList.Add((IPair)list.GetElementAt(i));
pointsArray = tempList.ToArray();
}
else
pointsArray = (IPair[])element;
if (pointsArray.Length > 0)
{
List<long> time = new List<long>(pointsArray.Length);
List<int> X = new List<int>(pointsArray.Length);
List<int> Y = new List<int>(pointsArray.Length);
for (int i = 0; i < pointsArray.Length; i++)
{
IPair pair = pointsArray[i];
long timePassed = (long)pair.Left;
IPoint point = (IPoint)pair.Right;
//Нужно, чтобы время создания каждой последующей точки ломаной было больше или равно времени создания предыдущей.
//Проконтролируем это.
if (time.Count == 0)
{
if ((pointCreationPassedTimeList.Count > 0) && (timePassed < pointCreationPassedTimeList[pointCreationPassedTimeList.Count - 1]))
throw new Exception("Ожидается, что переданное время создания новой точки больше или равно времени создания последней существующей точки списка. " +
"Время создания каждой последующей точки ломаной должно быть больше или равно времени создания предыдущей. " +
"Переданное же вами значение времени создания новой точки меньше, чем время создания последней точки ломаной.");
}
else
if (timePassed < time[time.Count - 1])
throw new Exception("Ожидается, что переданное время создания новой точки больше или равно времени создания последней существующей точки списка. " +
"Время создание одной из точек переданного списка меньше времени создания предыдущей точки. Упорядочите точки по возрастанию времени прежде чем передавать их в метод.");
//Нет, я бы и сам, конечно, мог бы упорядочить, но сейчас 6 часов утра и мне лень. Если не лень вам - перепишите метод.
time.Add(timePassed);
X.Add(point.X);
Y.Add(point.Y);
}
//Похоже на то, что в структуре полученного в качестве аргумента списка ошибок нет.
//Перенесём данные из временных списков в наши, хранящие информацию о точках ломаной.
for (int i = 0; i < pointsArray.Length; i++)
{
pointCreationPassedTimeList.Add(time[i]);
pointList.Add(new System.Drawing.Point(X[i], Y[i]));
}
}
}
catch (Exception exc)
{ ErrorReporter.ShowExceptionMessage(exc); }
LocalRelease();
FirePointListChangedEvent();
return;
}
Теперь в метод можно передавать массив IPair-ов, где левое значение пары – время в мс от создания сервера, а правое значение – IPoint.
Кроме того, я изменил метод Clear. Раз уж мы всё равно не храним историю удаленных точек, то теперь в этом методе будем заново инициализировать переменную creationTime (которая хранит время создания сервера). Таким образом, после каждого вызова Clear будем считать, что сервер создан только что, и отсчёт времени создания новых последующих точек будет начинаться с нуля. Итак, новый код метода:
/// <summary> Очищает список точек ломаной. </summary>
public void Clear()
{
accessControl.ResourceUsed();
LocalLock();
creationTime = DateTime.Now; // Обнуляем время создания экземпляра сервера.
pointList.Clear();
pointCreationPassedTimeList.Clear();
LocalRelease();
FirePointListChangedEvent();
}