Код к статье : Демонстрационный проект на VB - http://www.divil.co.uk/net/articles/plugins/pluginssample.zip
Архитектура плагинов
Наилучшим путём для реализации поддержки плагинов в приложении является поддержка интерфейсов. В этой статье нет основ работы с интерфейсами, но тут они играют существенную роль. Любой класс, реализующий интерфейс, должен реализовать каждый член интерфейса, так что любое приложение, знающее об интерфейсе, точно знает чего от него ожидать.
Написание приложения начнём с создания библиотеки классов с интерфейсами. Обычно используются как минимум два интерфейса. Можно обойтись и одним, который и будет реализован каждым классом плагина. Однако на практике нужен и второй интерфейс, который будет реализован в приложении-хосте для того, чтобы плагины могли иметь обратную связь с приложением. После компиляции библиотеки классов с интерфейсами мы создадим приложение, которое ссылается на эту библиотеку и может обследовать DLLки на предмет наличия классов, реализующих наши интерфейсы. На этом этапе можно разрабатывать собственно плагины, сделав ссылку на библиотеку классов и реализовав её интерфейсы.
Пишем интерфейсы
Сначала создадим проект типа Class Library и определим в нём 2 простых интерфейса. Каждый плагин будет иметь свойство с именем плагина и функцией, принимающей 2 целых числа и возвращающих число типа double . Главное приложение будет содержать такой метод, с помощью которого плагин сможет отобразить окно с сообщением в этом приложении. При создании нового проекта типа Class Library, мы по умолчанию получим в нём уже готовый класс. Его нужно удалить и определить интерфейс:
using System;
namespace hDrummer.clInterfaces {
/// <summary>
/// IPlugin
/// </summary>
public interface IPlugin
{
void Initialize(IHost host);
string Name { get; }
double Calculate(int i1, int i2);
}
/// <summary>
/// IHost
/// </summary>
public interface IHost {
void ShowFeedBack(string strFeedBack);
}
}
И наконец установим выходную директорию для этой библиотеки в общий каталог, в который поместим приложение и плагины.
Пишем первый плагин
Поскольку для целей тестирования нам нужен хотя бы один плагин, то и займёмся его написанием. Снова создаем проект типа Class Library, устанавливаем директорию для вывода и создаём ссылку на нашу предыдущую библиотеку. Затем меняем предлагаемый класс следующим образом:
using System;
using hDrummer.clInterfaces;
namespace hDrummer.PluginLibrary
{
/// <summary>
/// Plugin class
/// </summary>
public class PluginSample: clInterfaces.IPlugin
{
private clInterfaces.IHost objHost;
public void Initialize(IHost host)
{
objHost = host;
}
public string Name
{
get
{
return "PluginSample1 - Adds two numbers";
}
}
public double Calculate (int i1, int i2)
{
return i1+i2;
}
}
}
Только что мы создали простой плагин, складывающий два числа. Но хотя мы принимаем ссылку на интерфейс приложения-хоста, однако мы не используем её. Это мы сделаем в следующем плагине.
Пишем приложение-хост
Создадим новый проект типа Windows Application. Первым делом добавим ссылку на только что созданную библиотеку классов и установим выходную директорию в ту же, что и для библиотеки классов (это делается в свойствах проекта-Configuration Properties-Build-Output Path - прим. переводчика.). Существенной частью данной статьи явлется процесс исследования DLL на предмет наличия плагинов, хранение информации о том, какие плагины доступны, инициализация и дальнейшее их использование. Для этого мы создадим класс в файле PluginServices.vb, который инкапсулирует все эти вещи. Для получения списка плагинов используем функцию FindPlugins, которая принимает строку, содержащую каталог для поиска плагинов; строку, содержащую имя интерфейса, по которой мы будем искать классы, реализующие его функциональность. Эта функция перебират все файлы с расширением .dll в указанном каталоге, загружает их с помощью метода Assembly.LoadFrom() и передаёт выполнение другой функции для инспектирования сборки.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
using System.IO;
namespace HostApplication
{
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components = null;
public AvailablePlugins[] FindPlugins(string strPath, string strInterface)
{
ArrayList Plugins = new ArrayList();
string[] strDLLs;
Assembly objDLL;
strDLLs = Directory.GetFileSystemEntries(strPath, "*.dll");
for (int intIndex=0;(intIndex<strDLLs.Length-1);intIndex++)
{
try
{
objDLL = Assembly.LoadFrom(strDLLs[intIndex]);
ExamineAssembly(objDLL, strInterface, Plugins);
}
catch (Exception ex)
{
//ошибка загрузки DLL - мы тут ничего не делаем
}
}
// тут надо посмотреть объявление переменной в VB проекте
//AvailablePlugin Results[Plugins.Count-1];
if (Plugins.Count!=0)
{
Plugins.CopyTo(Results);
return Results;
}
else {return null;}
}
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(488, 273);
this.Name = "Form1";
this.Text = "Form1";
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
} }
Как только все файлы проверены и исследованы, функция возвращает массив типа AvailablePlugin, если что-то найдено, или null, если не найдено ничего. Как видим, функция ExamineAssembly проверяет загруженные сборки. Функция ExamineAssembly перебирает все типы, экспортируемые сборкой, и использует метод GetInterface() для каждого из них, проверяя, не реализует ли он наш интерфейс. Конечно же, в метод передаётся строка, содержащая полностью квалифицированное имя интерфейса. В нашем случае это PluginSample.Interfaces.IPlugin . Если такой тип найден, то ссылка на него добавляется к нашей переменной класса ArrayList, в ней хранится полный путь к DLL и полное имя класса.
private static ExamineAssembly(Assembly objDLL, string strInterface, ArrayList Plugins){
Type objType;
Type objInterface;
AvailablePlugin Plugin;
//Цикл по всем типам в DLL
foreach (objType in objDLL.GetTypes()){
//Смотрим только типы public
if (objType.IsPublic == true) {
//игнорируем абстрактные классы
if ((objType.Attributes And TypeAttributes.Abstract) !=
TypeAttributes.Abstract){
//Смотрим, реализует ли этот тип наш интерфейс
objInterface = objType.GetInterface(strInterface, true);
if (objInterface != null){
Plugin = new AvailablePlugin();
Plugin.AssemblyPath = objDLL.Location;
Plugin.ClassName = objType.FullName;
Plugins.Add(Plugin);
}
}}}}
Наконец, напишем функцию, которая создаст экземпляр необходимого плагина. Она принимает структуру AvailablePlugin и возвращает оbject, который нужно привести к определённому типу в вызывающей процедуре.
public static object CreateInstance(AvailablePlugin Plugin){
Assembly objDLL;
object objPlugin;
try {
//Загружаем dll
objDLL = Assembly.LoadFrom(Plugin.AssemblyPath);
//создаём и возвращаем экземпляр класса
objPlugin = objDLL.CreateInstance(Plugin.ClassName);
}
catch (Exception e){return null;}
return objPlugin;
}}
Вот и всё о файле PluginServices.vb. Всё остальное дотаточно просто. В методе Main мы вызываем метод FindPlugins и заполняем с его помощью список на форме. Пользователь может выбрать какой-то плагин из этого списка, а также имеет возможность выбора двух чисел, запуска плагина и получения результата. Однако, ещё необходимо реализовать класс в приложении-хосте. Класс, который реализует интерфейс IHost и предложит плагинам способ для вызова методов приложения-хоста. В нашем случае такой метод просто отображает диалоговое окно с сообщением.. Я не стану дальше расписывать код приложения, в надежде, что код сам всё расскажет за себя. Однако стоит заметить, что в этом приложении мы всякий раз, при необходимости вычислений, создаём экземпляр плагина, хотя обычно время жизни такого экземпляра должно быть гораздо больше.
Ещё один плагин
Попробуем создать ещё один плагин, перемножающий два числа. Этот плагин будет перемножать два числа. Он также будет использовать интерфейс для отображения результата в диалогов окне. Код находится в папке [Plugin 2]. Вот и всё. Код к данному руководству у вас есть, так что можете использовать файл PluginServices.vb в своих приложениях. В модификациях он не нуждается.
Прим. переводчика: Скачайте проект на VB, чтобы посмотреть код целиком. Ссылка на него - в заголовке статьи.
Вот и всё, удачного программирования!
Перейти к рубрике --> C & C++ |