
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Создание первого плагина
Первый плагин должен предоставлять способ обозревать и редактировать данные точек ломаной. Он имеет возможность запрашивать данные точек с сервера и отправлять отредактированные данные обратно. Кроме того, он не должен напрямую использовать COM. Для того, чтобы его работа была возможной, мы реализовали дополнительный метод SetPoints в ZigzagControl. Я иногда использую слова «сервер» и «приложение-хост». Следует понимать, что это всё одно и то же приложение, просто термин «сервер» по отношению к плагинам как-то по-моему звучит некорректно, потому я стараюсь его использовать реже.
Вид главного окна плагина:
Для удобства я советую открыть солюшен первого плагина в примерах, там действительно очень много комментариев, которые делают код предельно понятным для всех, у кого был хоть какой-то, даже самый небольшой опыт программирования. Именно такой целью я и задавался, создавая данное руководство – предельная понятность.
Плагин – это отдельно компилируемая библиотека, потому создадим отдельное решение для нашего плагина. Запустите MS Visual Studio. Выберете «Создать проект».
Далее выбираем «Библиотека классов» и указываем имя проекта Plugin1.
Сразу же переименовываем файл Class1.cs в ZigzagPlugin.cs, соглашаемся с предложением переименовать класс в содержимом файла.
Добавляем новую ссылку.
Выбираем вкладку «Обзор», находим директорию с нашим сервером и указываем исполняемый файл в качестве ссылки. Он содержит .NET-сборку и все публичные типы оттуда будут экспортированы в наш проект.
Импорт типов с сервера
Нам потребуются некоторые типы, которые уже есть на сервере, а именно: Pair, Point, PluginConnectionInfo, PluginAction. Конечно, раз мы подключили сервер как ссылку, мы могли бы их использовать оттуда, но я хочу показать, что покуда у нас есть доступ к интерфейсам, то нам совершенно не обязательно опираться на чужие классы. Мы можем создавать свои реализации интерфейсов на стороне плагина и использовать их. Ведь серверу всё равно, с каким именно классом работать, для него существенно только то, какие интерфейсы эти классы реализуют. Потому сервер я только подключил для того, чтобы импортировать все его интерфейсы.
Реализуем вышеуказанные классы. Добавим файл Import.cs в проект.
В этом файле мы создадим несколько классов-аналогов серверным классам. Честно говоря, я просто собираюсь скопипастить серверные реализации этих классов и внести в них небольшие изменения. Итак, удаляем автоматически созданный класс Import и копипастим с сервера класс Pair.
/// <summary>
/// Пара, кортеж объектов длины 2.
/// Имеет левый и правый элемент.
/// </summary>
[ComVisible(true), Guid("9665CD7C-2CF6-4D73-B6C9-1EC865BE5931")]
public class Pair : IPair, IDisposable
{
object left, right;
/// <summary>
/// Конструктор экземпляра класса Pair.
/// </summary>
public Pair()
{
left = null;
right = null;
ComServer.Lock();
}
/// <summary>
/// Конструктор экземпляра класса Pair.
/// </summary>
/// <param name="left">Левый объект пары.</param>
/// <param name="right">Правый объект пары.</param>
public Pair(object left, object right)
{
this.left = left;
this.right = right;
ComServer.Lock();
}
#region Члены IPair
/// <summary>
/// Возвращает левый объект пары.
/// </summary>
public object Left
{
get { return left; }
}
/// <summary>
/// Возвращает правый объект пары.
/// </summary>
public object Right
{
get { return right; }
}
#endregion
#region Члены IDisposable
bool isDisposed = false;
/// <summary>
/// Уничтожает экземпляр класса.
/// </summary>
public void Dispose()
{
if (!isDisposed)
{
ComServer.Unlock();
isDisposed = true;
}
}
#endregion
/// <summary> Деструктор класса, который будет автоматически освобождать объект после того, как не осталось ни одной ссылки на него. </summary>
~Pair()
{
Dispose();
}
}
Смотрим, что в нём можно удалить. Во-первых, строчка «[ComVisible(true), Guid("9665CD7C-2CF6-4D73-B6C9-1EC865BE5931")]» нам больше не понадобится, мы не собираемся экспонировать объекты плагина в COM, удаляем её. Строки «ComServer.Lock();» тоже уже не нужны. Весь регион с методом Dispose и деконструктором ~Pair() – туда же. Так как интерфейс IPair находится в пространстве имён сервера, то заменяем строку объявления класса на вот эту:
public class Pair : ZigzagServer.IPair
Посмотрим, что у нас в итоге получилось.
/// <summary>
/// Пара, кортеж объектов длины 2.
/// Имеет левый и правый элемент.
/// </summary>
public class Pair : ZigzagServer.IPair
{
object left, right;
/// <summary>
/// Конструктор экземпляра класса Pair.
/// </summary>
public Pair()
{
left = null;
right = null;
}
/// <summary>
/// Конструктор экземпляра класса Pair.
/// </summary>
/// <param name="left">Левый объект пары.</param>
/// <param name="right">Правый объект пары.</param>
public Pair(object left, object right)
{
this.left = left;
this.right = right;
}
#region Члены IPair
/// <summary>
/// Возвращает левый объект пары.
/// </summary>
public object Left
{
get { return left; }
}
/// <summary>
/// Возвращает правый объект пары.
/// </summary>
public object Right
{
get { return right; }
}
#endregion
}
Как вы видете, это уже совершенно другой класс, но который так же реализовывает IPair, как и его серверный аналог.
Сходным образом создадим аналоги классов Point, PluginAction и PluginConnectionInfo. Выглядеть они будут вот так:
public class Point : ZigzagServer.IPoint
{
int x, y;
/// <summary>
/// Конструктор экземпляра класса Point.
/// </summary>
public Point()
{
x = 0;
y = 0;
}
/// <summary>
/// Конструктор экземпляра класса Point.
/// </summary>
/// <param name="x">X-координата точки.</param>
/// <param name="y">Y-координата точки.</param>
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
#region Члены IPoint
/// <summary>
/// Получает X-координату точки.
/// </summary>
public int X
{
get { return x; }
}
/// <summary>
/// Получает Y-координату точки.
/// </summary>
public int Y
{
get { return y; }
}
#endregion
}
/// <summary>
/// Информация о плагине
/// </summary>
public class PluginConnectionInfo : ZigzagServer.IPluginConnectionInfo
{
string pluginName, pluginDescription;
ZigzagServer.IPluginAction[] actions = new ZigzagServer.IPluginAction[0];
bool isInited = false;
#region Члены IPluginConnectionInfo
/// <summary>
/// Получает имя плагина.
/// </summary>
public string PluginName
{
get { return pluginName; }
}
/// <summary>
/// Получает описание плагина.
/// </summary>
public string PluginDescription
{
get { return pluginDescription; }
}
/// <summary>
/// Получает массив возможных в плагине действий.
/// </summary>
public ZigzagServer.IPluginAction[] AvailableActions
{
get { return actions; }
}
/// <summary>
/// Инициализирует экземпляр информации подключения плагина.
/// </summary>
/// <param name="pluginName">Имя плагина.</param>
/// <param name="pluginDescription">Описание плагина.</param>
/// <param name="pluginActions">Список возможных действий, которые предоставляет плагин.</param>
public void Init(string pluginName, string pluginDescription, ZigzagServer.IPluginAction[] pluginActions)
{
try
{
this.pluginName = pluginName;
this.pluginDescription = pluginDescription;
actions = (ZigzagServer.IPluginAction[])pluginActions.Clone();
isInited = true;
}
catch { }
}
/// <summary>
/// Получает признак того, что экземпляр информации подключения плагина инициализирован.
/// </summary>
public bool IsInitialized
{
get { return isInited; }
}
#endregion
/// <summary>
/// Инициализирует экземпляр информации подключения плагина.
/// </summary>
/// <param name="pluginName">Имя плагина.</param>
/// <param name="pluginDescription">Описание плагина.</param>
/// <param name="pluginActions">Список возможных действий, которые предоставляет плагин. Сюда следует передавать массив IPluginAction-ов</param>
public PluginConnectionInfo(string pluginName, string pluginDescription, ZigzagServer.IPluginAction[] pluginActions)
{
try
{
Init(pluginName, pluginDescription, pluginActions);
}
catch { }
}
public PluginConnectionInfo()
{
}
}
/// <summary>
/// Действие, доступное из плагина.
/// </summary>
public class PluginAction : ZigzagServer.IPluginAction
{
int actionId;
string actionName;
string actionDescription;
bool isInited = false;
public PluginAction(int actionId, string actionName, string actionDescription)
{
Init(actionId, actionName, actionDescription);
}
public PluginAction()
{
}
#region Члены IPluginAction
/// <summary>
/// Получает идентификатор действия.
/// </summary>
public int ActionId
{
get { return actionId; }
}
/// <summary>
/// Получает строку помощи по данному действию.
/// </summary>
public string ActionDescription
{
get { return actionDescription; }
}
/// <summary>
/// Получает имя действия. Это значение будет отображено в меню.
/// </summary>
public string ActionName
{
get { return actionName; }
}
/// <summary>
/// Получает признак того, что экземпляр действия плагина инициализирован.
/// </summary>
public bool IsInitialized
{
get { return isInited; }
}
/// <summary>
/// Инициализирует экземпляр действия плагина.
/// </summary>
/// <param name="actionId">Идентификатор действия.</param>
/// <param name="actionName">Имя действия. Это значение будет отображено в меню.</param>
/// <param name="actionDescription">Описание действия.</param>
public void Init(int actionId, string actionName, string actionDescription)
{
this.actionId = actionId;
this.actionName = actionName;
this.actionDescription = actionDescription;
isInited = true;
}
#endregion
}