
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Код класса Plugin.Cs
На случай, если я что-то забыл, привожу здесь получившийся код файла целиком.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
namespace ZigzagServer
{
#region Исключения, связанные с плагинами
class CannotLoadDllAsPluginException : Exception
{
string dllPath;
public CannotLoadDllAsPluginException(string dllPath)
{ this.dllPath = dllPath; }
public string DllPath
{ get { return dllPath; } }
public override string Message
{ get { return "Невозможно загрузить dll как плагин сервера СПО: " + dllPath; } }
}
class InternalPluginErrorException : Exception
{
int errorCode;
public InternalPluginErrorException(int errorCode)
{ this.errorCode = errorCode; }
public int ErrorCode
{ get { return errorCode; } }
}
class PluginNotConnectedYetException : Exception
{
public PluginNotConnectedYetException()
{ }
}
#endregion
class Plugin : IDisposable
{
/// <summary> Путь к плагину. </summary>
string path;
#region Набор булевых признаков класса.
/// <summary> Показывает, написан ли плагин в управляемом коде, т.е. на семействе языков платформы .NET. </summary>
bool isManaged = false;
/// <summary> Показывает, содержит ли плагин COM-сервер. </summary>
bool isCOMServer = false;
/// <summary> Показывает, подключен ли плагин. </summary>
bool isConnected = false;
#endregion
#region Импорт из kernel32.dll для взаимодействия с неуправляемыми библиотеками.
/// <summary>
/// Загружает библиотеку с заданным путём в память приложения и возвращает её хэндл.
/// </summary>
/// <param name="dllToLoad">Путь к библиотеке.</param>
/// <returns>Хэндл загруженной библиотеки.</returns>
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);
/// <summary>
/// Получает указатель процедуры библиотеки по имени процедуры и хэндлу библиотеки.
/// </summary>
/// <param name="hModule">Хэндл библиотеки.</param>
/// <param name="procedureName">Имя процедуры.</param>
/// <returns>Возвращает указатель на процедуру.</returns>
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
/// <summary>
/// Закрывает библиотеку и выгружает её из памяти приложения.
/// </summary>
/// <param name="hModule">Хэндл библиотеки.</param>
/// <returns>Возвращает булевый признак успешного завершения операции</returns>
[DllImport("kernel32.dll")]
private static extern bool FreeLibrary(IntPtr hModule);
#endregion
#region Делегаты для привзяки к неуправляемым процедурам.
private delegate string GetCOMClassNameDelegate();
private delegate int ConnectDelegate(object iZigzagControl, ref object iPluginConnectionInfo);
private delegate int DisconnectDelegate();
private delegate int PerformActionDelegate(int actionId);
/// <summary>
/// Вызов процедуры получения имени COM-класса плагина, содержащейся в unmanaged-библиотеке.
/// </summary>
private GetCOMClassNameDelegate GetCOMClassNameUnmanaged;
/// <summary>
/// Вызов процудуры подключение к плагину, содержащейся в unmanaged-библиотеке.
/// </summary>
private ConnectDelegate ConnectUnmanaged;
/// <summary>
/// Вызов процудуры отключения от плагина, содержащейся в unmanaged-библиотеке.
/// </summary>
private DisconnectDelegate DisconnectUnmanaged;
/// <summary>
/// Вызов процудуры выполнения действия плагина с заданным идентификатором, содержащейся в unmanaged-библиотеке.
/// </summary>
private PerformActionDelegate PerformActionUnmanaged;
#endregion
#region Переменные для вызова методов из управляемых библиотек, не содержащих COM-сервера.
//Сделаны для поддержки Framework 3.5. В Framework 4 можно использовать тип dynamic и не обламываться.
private MethodInfo connectMethod, disconnectMethod, performActionMethod, getComClassNameMethod;
#endregion
#region Информация о плагине, полученная из его IPluginConnectionInfo
/// <summary> Информация о плагине, полученная при подключении. </summary>
IPluginConnectionInfo pluginConnectionInfo;
/// <summary> Имя плагина. </summary>
private string pluginName;
/// <summary> Описание плагина. </summary>
private string pluginDescription;
/// <summary> Список действий плагина. </summary>
private List<IPluginAction> actions = new List<IPluginAction>();
#endregion
/// <summary> Содержит COM-объект с плагином. Используется как для плагинов в управляемом коде, так и для плагинов в коде неуправляемом. </summary>
private IPlugin COMPlugin;
/// <summary> Хэндл библиотеки с неуправляемым плагином. </summary>
private IntPtr unmanagedPluginDllHandle;
/// <summary> Экземлпяр класса ZigzagControl. </summary>
private IZigzagControl iZigzagControl;
#region Поля класса
/// <summary> Получает имя плагина. </summary>
public string Name
{
get
{ return pluginName; }
}
/// <summary> Получает описание плагина. </summary>
public string Description
{
get
{ return pluginDescription; }
}
/// <summary> Получает список действий, которые плагин может выполнить. </summary>
public List<IPluginAction> ActionsList
{
get
{ return new List<IPluginAction>(actions.ToArray()); }
}
/// <summary> Получает состояние подключенности плагина. </summary>
public bool IsConnected
{
get
{ return isConnected; }
}
#endregion
#region Функционал класса
/// <summary>
/// Конструктор класса Plugin.
/// </summary>
/// <param name="dllPath">Путь к библиотеке, содержащей плагин.</param>
public Plugin(string dllPath)
{
// Сохраним путь и проверим наличие файла.
path = dllPath;
System.IO.FileInfo fi = new System.IO.FileInfo(dllPath);
if (!fi.Exists)
throw new System.IO.FileNotFoundException("dll-файл плагина не найден.", dllPath);
// Попытаемся найти в библиотеке сборку. Если библиотека написана в управляемом коде, то она содержит её и всё будет ок, иначе программа выбросит исключение.
try
{
// Грузим либу.
Assembly pluginAssembly = Assembly.LoadFile(path);
isManaged = true;
// Будем считать, что управляемая библиотека должна содержать тип ZigzagPlugin
// Если библиотека содержит внутренний COM-сервер, то этот тип содержит метод string GetCOMClassName(), который возвращает имя COM-класса при его вызове.
// В противном случае он содержит все те методы, которые объявлены в IPlugin.
Type spoServerPluginType = pluginAssembly.GetType("ZigzagPlugin"); //Получим тип через reflection.
getComClassNameMethod = spoServerPluginType.GetMethod("GetCOMClassName", System.Type.EmptyTypes); //Пошмонаем его на наличие нужных нам методов.
// В GetMethod передаётся имя искомой функции и массив типов её аргументов
if (getComClassNameMethod != null)
{
// Если метод, возвращающий имя COM-класса найден, стало быть либа содержит COM-сервер и остальные
// три метода искать нет смысла -- получим их через IPlugin в тот момент, когда потребуются, а не сейчас.
isCOMServer = true;
return;
}
// В противном случае -- поищем остальные методы.
connectMethod = spoServerPluginType.GetMethod("Connect", new Type[] { typeof(object), typeof(object).MakeByRefType() });
disconnectMethod = spoServerPluginType.GetMethod("Disconnect", System.Type.EmptyTypes);
performActionMethod = spoServerPluginType.GetMethod("PerformAction", new Type[] { typeof(int) });
if ((connectMethod != null) && (disconnectMethod != null) &&
(performActionMethod != null))
return; // Все необходимые методы нашлись, работа конструктора завершена.
}
catch (Exception)
{ }
// Если выполнение дошло до сюда, значит наша гипотеза о том, что плагин написан в управляемом коде, была неверна.
isManaged = false;
try
{
//Здесь логика работы аналогичная, только уже не через reflection, а через interop.
//Грузим либу.
unmanagedPluginDllHandle = LoadLibrary(path);
//Ищем функции.
IntPtr getComClassNameMethodPtr = GetProcAddress(unmanagedPluginDllHandle, "GetCOMClassName");
if (getComClassNameMethodPtr != IntPtr.Zero)
{
// Если метод, возвращающий имя COM-класса найден, стало быть либа содержит COM-сервер и остальные
// три метода искать нет смысла -- получим их через IPlugin в тот момент, когда потребуются, а не сейчас.
isCOMServer = true;
GetCOMClassNameUnmanaged = (GetCOMClassNameDelegate)Marshal.GetDelegateForFunctionPointer(getComClassNameMethodPtr, typeof(GetCOMClassNameDelegate));
return;
}
// Это не COM, ищем другие методы.
IntPtr connectMethodPtr = GetProcAddress(unmanagedPluginDllHandle, "Connect");
IntPtr disconnectMethodPtr = GetProcAddress(unmanagedPluginDllHandle, "Disconnect");
IntPtr performActionPtr = GetProcAddress(unmanagedPluginDllHandle, "PerformAction");
if ((connectMethodPtr != IntPtr.Zero) && (disconnectMethodPtr != IntPtr.Zero) && (performActionPtr != IntPtr.Zero))
{
#region Работа этой секции кода не тестировалась
ConnectUnmanaged = (ConnectDelegate)Marshal.GetDelegateForFunctionPointer(connectMethodPtr, typeof(ConnectDelegate));
DisconnectUnmanaged = (DisconnectDelegate)Marshal.GetDelegateForFunctionPointer(disconnectMethodPtr, typeof(DisconnectDelegate));
PerformActionUnmanaged = (PerformActionDelegate)Marshal.GetDelegateForFunctionPointer(performActionPtr, typeof(PerformActionDelegate));
return; // Все необходимые методы нашлись, работа конструктора завершена.
#endregion
}
}
catch (Exception)
{ }
throw new CannotLoadDllAsPluginException(path); // Плагин какой-то палёный, выбрасываем исключение.
}
/// <summary>
/// Заполняет поля с информацией о плагине значениями из IPluginConnectionInfo
/// </summary>
/// <param name="info"></param>
private void FillInfo(IPluginConnectionInfo info)
{
pluginName = info.PluginName;
pluginDescription = info.PluginDescription;
IPluginAction[] pluginActions = info.AvailableActions;
actions = new List<IPluginAction>(pluginActions);
}
/// <summary>
/// Подключает плагин к приложению.
/// </summary>
/// <param name="iZigzagControl">Экземпляр класса ZigzagControl.</param>
/// <returns>Возвращает 0, если плагин подключился успешно, иначе возвращает код ошибки.</returns>
public int Connect(IZigzagControl iZigzagControl)
{
this.iZigzagControl = iZigzagControl;
if (!isConnected)
{
int result = 0;
object connectionInfo = null;
pluginConnectionInfo = null;
if (isCOMServer)
{
// Преимуществом технологии COM является то, что можно единообразно обращаться как с managed, так и c unmanaged-кодом.
// Различия будут лишь в способе вызова метода получения имени COM-класса
string typeName;
// Сначала получим имя класса.
if (isManaged)
typeName = (string)getComClassNameMethod.Invoke(null, null);
else
typeName = GetCOMClassNameUnmanaged();
if ((typeName == null) || (typeName == ""))
return -404;
Type type = Type.GetTypeFromProgID(typeName); // Получим экземпляр класса по его имени.
object plugin = Activator.CreateInstance(type); // Создадим его. Будет использован конструктор по умолчанию.
COMPlugin = plugin as IPlugin; // Прикастим к нашему интерфейсу.
result = COMPlugin.Connect(iZigzagControl, ref connectionInfo); // Подключим.
pluginConnectionInfo = (IPluginConnectionInfo)connectionInfo; // Получим информацию о нём.
if ((pluginConnectionInfo == null) || (result != 0))
throw new InternalPluginErrorException(result);
FillInfo(pluginConnectionInfo); // Заполним поля нашего класса полученной информации.
isConnected = true;
return result; // Готово.
}
if (isManaged)
{
object[] args = new object[] { iZigzagControl, connectionInfo }; // Создаем массив с аргументами.
result = (int)connectMethod.Invoke(null, args); // Вызываем исполнение метода.
pluginConnectionInfo = (IPluginConnectionInfo)args[1]; // Если метод выполнился без ошибок, то во втором аргументе будет инфа о плагине.
if ((pluginConnectionInfo == null) || (result != 0))
throw new InternalPluginErrorException(result);
FillInfo(pluginConnectionInfo); //Заполняем поля класса
isConnected = true;
return result;
}
else
{
#region Работа этой секции кода не тестировалась
result = ConnectUnmanaged(iZigzagControl, ref connectionInfo);
pluginConnectionInfo = (IPluginConnectionInfo)connectionInfo;
if ((pluginConnectionInfo == null) || (result != 0))
throw new InternalPluginErrorException(result);
FillInfo(pluginConnectionInfo);
isConnected = true;
return result;
#endregion
}
}
else
return 0;
}
/// <summary>
/// Отключает плагин от приложения.
/// </summary>
/// <returns>Возвращает 0, если плагин подключился успешно, иначе возвращает код ошибки.</returns>
public int Disconnect()
{
try
{
if (isConnected)
{
int result = 0;
if (isCOMServer)
{
result = COMPlugin.Disconnect();
if (!isManaged)
FreeLibrary(unmanagedPluginDllHandle);
if (result != 0)
throw new InternalPluginErrorException(result);
isConnected = false;
//((IDisposable)iZigzagControl).Dispose();
return result;
}
if (isManaged)
{
result = (int)disconnectMethod.Invoke(null, null);
if (result != 0)
throw new InternalPluginErrorException(result);
isConnected = false;
//((IDisposable)iZigzagControl).Dispose();
return result;
}
else
{
#region Работа этой секции кода не тестировалась
result = DisconnectUnmanaged();
if (result != 0)
throw new InternalPluginErrorException(result);
FreeLibrary(unmanagedPluginDllHandle);
isConnected = false;
//((IDisposable)iZigzagControl).Dispose();
return result;
#endregion
}
}
else
return 0;
}
catch (Exception)
{ return 1; }
}
/// <summary>
/// Выполняет действие, доступное из плагина.
/// </summary>
/// <param name="actionId">Идентификатор действия.</param>
/// <returns>Возвращает 0, если плагин подключился успешно, иначе возвращает код ошибки.</returns>
public int PerformAction(int actionId)
{
if (isConnected)
{
int result = 0;
if (isCOMServer)
{
// В случае, если плагин имеет COM-сервер, просто запрашиваем выполнение действия и контроллируем результат.
result = COMPlugin.PerformAction(actionId);
if (result != 0)
throw new InternalPluginErrorException(result);
return result;
}
//Иначе, в зависимости от управляемости кода, запрашиваем выполнение его другими путями.
if (isManaged)
{
try
{
result = (int)performActionMethod.Invoke(null, new object[] { actionId });
if (result != 0)
throw new InternalPluginErrorException(result);
}
catch (TargetInvocationException)
{ result = -100; }
return result;
}
else
{
#region Работа этой секции кода не тестировалась
result = PerformActionUnmanaged(actionId);
if (result != 0)
throw new InternalPluginErrorException(result);
return result;
#endregion
}
}
else
throw new PluginNotConnectedYetException();
}
#endregion
#region Члены IDisposable
/// <summary>
/// Деструктор класса, который будет автоматически освобождать объект после того, как не осталось ни одной ссылки на него.
/// </summary>
public void Dispose()
{
if (isConnected)
Disconnect();
}
#endregion
}
}
Смотрите под лупой либо копируйте куда-то.