
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Оглавление
Добавление к COM-серверу поддержки событий 4
Создание модели взаимодействия приложения хоста с плагинами 7
Описание COM-интерфейсов, связанных с поддержкой плагинов 9
Создание COM-интерфейсов, связанных с поддержкой плагинов 10
Создание COM-классов, связанных с поддержкой плагинов 14
Создание класса, отвечающего за взаимодействие с плагинами 18
Код класса Plugin.cs 31
Внесение косметических изменений в серверную часть 33
Строим своё меню с плагинами и идентификаторами 37
Вносим изменения в главную форму сервера 41
Создание первого плагина 50
Импорт типов с сервера 52
Создание класса для хранения внутреннего представления точек 58
Создание форм редактирования данных о точках 59
Реализация форм редактирования данных о точках 62
Реализация класса первого плагина 72
Создание третьего плагина 75
Делаем наш плагин COM-видимым 76
Создание главной формы плагина 78
Реализация класса третьего плагина 88
Создание инсталлятора для плагина 91
Создание второго плагина 98
Создание главной формы плагина 102
Создание библиотеки типов 106
Реализация формы второго плагина 115
Реализация класса второго плагина 128
Добавление метода GetCOMClassName 134
Data Execution Prevention и его отключение 135
Тестирование совместной работы клиента и плагинов 139
Исходный код 140
Создание приложения-хоста плагинов в среде MSVS 2010
В данном гайде описан процесс добавления функционала, позволяющего любому приложению подключать к себе плагины. Приложение-хост будет способно подключать к себе плагины написанные как на языках платформы .NET Framework, так и на других языках, на которых возможно создание динамических библиотек. Также в руководстве будет показан процесс создания в среде Borland C++ Builder плагина, содержащего в себе внутренний (InProc) COM-сервер. Я старался сделать инструкцию очень подробной и наглядной, чтобы с задачей мог справиться даже пьяный десятилетний ребёнок. Предполагается, что вы уже имеете представление о работе с MS Visual Studio и имели опыт написания проектов на C# и С++. Также желательно было бы иметь базовые сведения о Component Object Model, но это совершенно не обязательно. Но всё же лучше понимать, что ты делаешь и как оно работает, чем просто бездумно выполнять заданные шаги. Некоторые тонкие моменты я буду объяснять.
В ходе работы я буду модифицировать свою программу, написанную во время создания гайда «Использование серверов автоматизации MS Word и MS Excel в среде MSVS 2010». Вы его легко найдете поиском документов во вконтакте по запросу «Использование серверов автоматизации». На всякий случай, вот вам ссылка, но её перманентность не гарантирую. Если вы не читали этот гайд и не хотите, то могу только рекомендовать вам прочитать хотя бы вступление, чтобы примерно понимать, что программа вообще делает. Я буду умалчивать большинство технических деталей, которые были озвучены там. Изначальную версию исходников модифицируемой в ходе этого руководства программы можно найти здесь.
Плагин – это независимо компилируемый программный модуль, динамически подключаемый к основной программе, предназначенный для расширения и/или использования её возможностей. Поддержка плагинов может значительно повысить интерес к вашей программе со стороны программистов, которые теперь смогут дополнять её функционал или перенаправлять данные из программы куда-то ещё. Конечные пользователи же смогут кастомизировать ваше приложение так, как они хотят, добавляя те функции, которых им необходимы. Пожалуй, половина популярности Miranda IM заработала из-за огромного множества плагинов к ней, так что поддержка плагинов может сильно помочь в распространении вашей программы. Для создания плагина нам необходимо будет разработать интерфейс взаимодействия между плагинами и хостом.
И не пугайтесь объёма документа – там много кода и картинок. Очень рекомендую по ходу чтения смотреть в пример исходников. Ссылку на готовые, хорошо документированные исходники, которые получились у меня после написания данной статьи, можно найти в конце этого руководства.
Если вы вдруг обнаружили какие-то ошибки, как то: грамматические, стилистические и тем более ошибки в содержимом и алгоритмах, сообщите, пожалуйста, мне по электронной почте, указанной на титульном листе.
Задача:
Используя технологию создания плагинов дополнить сервер автоматизации функциональностью, позволяющей выполнить все нижеперечисленные действия. Приложение должно быть способно подключать плагины в виде dll-файла из любой директории. Необходимо предоставить пользователю возможность вызывать действия, доступные из плагина, через главное окно сервера.
Разработать Плагин_1, который должен содержать визуальное представление таблицы базы данных (из сервера автоматизации) и элементы управления для навигации и редактирования. Плагин должен работать без COM-вызовов.
Разработать Плагин_2, который должен воспроизводить записанную последовательность действий из базы данных сервера автоматизации (при этом на экране визуально воспроизводятся действия, которые выполнял пользователь вместе со всеми временными задержками). Процесс воспроизведения повторяется в цикле, управляемом кнопкой воспроизведения и остановки. Т.е. имеется кнопка, при нажатии на которую, в окне плагина запускается процесс воспроизведения. Процесс воспроизведения повторяется в цикле, пока не будет нажата кнопка остановки. Плагин должен быть создан в среде Borland C++ Builder и должен содержать в себе внутренний (In-Proc) COM-сервер.
Разработать Плагин_3, который должен строить динамическую диаграмму записанных действий в базе данных сервера автоматизации. На диаграмме должна быть отражена зависимость координаты Y последних 8 точек от времени их создания. Процесс воспроизведения повторяется в цикле, управляемом кнопкой воспроизведения и остановки. Плагин должен содержать в себе внутренний (In-Proc) COM-сервер.
Добавление к com-серверу поддержки событий
Итак, у нас есть COM-сервер из предыдущих моих гайдов. Сначала я решил его немного доработать и добавить туда поддержку событий. Сейчас, если запустить несколько клиентов и добавлять через них новые точки к серверу, то только на одном клиенте будут содержаться актуальные данные о точках, ибо клиент запрашивает с сервера список точек только при запуске и добавлении/очистке точек. Было бы неплохо, если бы сервер генерировал событие, а все подключенные в текущий момент клиенты по нему имели возможность получить список точек.
В COM-технологии есть возможность создавать события на COM-серверах через так называемые Connection Point. Я пока практически не разбирался в их работе, если вам интересно и вы знаете английский, то почитать можно здесь, например. На MSDN про COM-события есть и на русском, но там больше о использовании COM-событий через платформу фреймворка нежели чем о механизме событий в COM вообще.
Примерная суть работы с событиями такова: COM-сервер объявляет некоторый так называемый source interface, который содержит список методов, каждый из которых соответствует возникновению некоторого события. Клиент, желающий реагировать на событие, создает для этого интерфейса так называемый Sink, объект-обработчик событий, реализующий этот интерфейс. Source interface-ов у сервере может быть и несколько.
Модифицируем сервер и клиент таким образом, чтобы они поддерживали события. Откроем проект сервера и добавим в папку Interfaces новый интерфейс IZigzagControlEvents.
Напишем код интерфейса:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ZigzagServer
{
[ComVisible(true), Guid("9665CD7C-2CF6-4D73-B6C9-1EC865BE5926")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IZigzagControlEvents
{
[DispId(1)]
void PenChanged();
[DispId(2)]
void PointListChanged();
}
}
Обратите внимание на то, что этот интерфейс имеет тип IDispatch и каждый его метод имеет DispId. По неизвестным мне причинам, если не сделать так, то работать не будет. По крайней мере у меня не заработало, вы же свободны в своих экспериментах и изысканиях. Если вдруг узнаете причину, пишите мне на мыло.
Теперь немного поправим класс ZigzagControl. Заменим его объявление на:
[ComVisible(true), Guid("9665CD7C-2CF6-4D73-B6C9-1EC865BE5933")]
[ComSourceInterfaces(typeof(IZigzagControlEvents))]
public class ZigzagControl : IZigzagControl, ILocker, IDisposable
Этим самым мы добавим к нему источник событий. Теперь нужно изменить клиент. Добавим парочку необходимых делегатов в файл COMImport.cs.
public delegate void PointListChangedDelegate();
public delegate void PenChangedDelegate();
Изменим метод ClientForm_Load файла ClientForm.cs.
private void ClientForm_Load(object sender, EventArgs e)
{
Type zigzagControlType = Type.GetTypeFromProgID("ZigzagServer.ZigzagControl");
if (zigzagControlType == null)
{
MessageBox.Show("Реестр не содержит данных о типе ZigzagControl. Возможно сервер не установлен или не зарегистрирован");
Close();
}
//zigzagControl = (IZigzagControl)Activator.CreateInstance(zigzagControlType);
zigzagControl = Activator.CreateInstance(zigzagControlType);
zigzagControl.PenChanged += new PenChangedDelegate(ClientForm_PenChanged);
zigzagControl.PointListChanged += new PointListChangedDelegate(ClientForm_PointListChanged);
RefreshServerStatus();
}
Как видите, мы добавили обработчики событий изменения пера и изменения списка точек ломаной. Напишем эти обработчики.
void ClientForm_PointListChanged()
{
RefreshServerStatus();
}
void ClientForm_PenChanged()
{
GetCurrentPen();
pictureBox.Image = GetCurrentImage();
}
Сейчас получается так, что клиент загружает с сервера список точек два раза после каждого своего действия, ведь до этого у нас клиент получал список точек после клика по своей рабочей области. Теперь, когда мы сделали по-людски, через обработчики события, загрузку списка точек из метода добавления новой точки нужно убрать – точки сами загрузятся из обработчика. Изменим для этого обработчик события клика в клиенте.
private void pictureBox_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
zigzagControl.AddNewPoint(e.X, e.Y);
//RefreshServerStatus();
}
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
zigzagControl.Lock();
try
{
zigzagControl.ClearPoints();
zigzagControl.SetNewRandomPen();
}
catch { }
zigzagControl.ReleaseLock();
//RefreshServerStatus();
}
}
Комментариями здесь отмечены удаляемые строки.