Пример системы плагинов
Придумал я себе такую задачу, как разработать к программе систему плагинов (add-in-ов). Немного поковыряв AddIn которые были предложены начиная с .NET 3.5, понял что они мне не подходят. Во-первых это не удобное расположение каталогов, много лишних. Во-вторых не возможно внедриться в какую нибудь часть процесса и сделать по своему, т.е. слабая расширяемость. И решил изобрести велосипед…
В основу я поставил изолированность от главного приложения и возможность песочницы. Это все не сложно сделать создав новый AppDomain и сконфигурировав его на InternetZone. Но при разработке я столкнулся с проблемой при загрузке сборки с плагином в домен. Проблема заключалась в ошибке со связями. Для загрузки я использую AppDomain.Load(). Как позже оказалось этот метод мне не подходит. По-этому пришлось сделать не большой костыль, о котором я и расскажу.
Начнем с создания нового проекта и консольного приложения (я назвал его PluginHost) и добавим еще один проект библиотеку классов (назовем её PluginFramework). В библиотеку добавим класс:
public abstract class Plugin: MarshalByRefObject
{
public virtual void Initialize()
{
}
}
И еще один класс, без которого было сложно реализовать необходимую мне логику:
public class AssemblyHelper: MarshalByRefObject
{
private AppDomain _currentDomain;
public AssemblyHelper()
{
_currentDomain = AppDomain.CurrentDomain;
_currentDomain.AssemblyResolve += new ResolveEventHandler(_currentDomain_AssemblyResolve);
}
Assembly _currentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
{
string[] nameSplit = e.Name.Split(',');
string path = Path.Combine(SearchFolder, nameSplit[0] + ".dll");
Assembly loadedAssembly;
try
{
loadedAssembly = Assembly.LoadFile(path);
}
catch (Exception exc)
{
Exception exp = exc;
throw;
}
if (loadedAssembly != null)
{
return loadedAssembly;
}
else
{
return null;
}
}
public string SearchFolder { get; set; }
}
Базовый каркас готов. Не большое отступление чего же я собственно хотел добиться:
- Еще раз изолированность от основного приложения
- В любой момент загрузка и выгрузка плагина. Если к примеру плагин упал, мы его просто выгрузим и заново загрузим
Что же у нас в Main:
Для начала я загружая сборку PluginFramework в режиме только для рефлексии и получаю Type нашего базового класса для всех плагинов
Assembly frameworkReflect =
Assembly.ReflectionOnlyLoadFrom(Path.Combine(Environment.CurrentDirectory, "PluginFramework.dll"));
Type basePluginType = frameworkReflect.GetType("MyPluginSample.Framework.Plugin");
А теперь поиск по всем имеющимся папкам
string pathPlugins = Path.Combine(Environment.CurrentDirectory, "Plugins");
DirectoryInfo directoryPlugins = new DirectoryInfo(pathPlugins);
foreach (var dir in directoryPlugins.GetDirectories())
{
FileInfo dllFile = dir.GetFiles(dir.Name + ".dll").First();
И опять я загружаю сборку в режиме только для рефлексии, но уже с плагином и ищу класс который наследуется от базового класса Plugin. Что бы мы здесь могли загрузить сборку с плагином и сравним с базовым типом нам и пришлось в начале PluginFramework тоже загрузить только для рефлексии.
Assembly reflectionAsm = Assembly.ReflectionOnlyLoadFrom(dllFile.FullName); Type typePlugin = reflectionAsm.GetTypes().First(t => t.IsSubclassOf(basePluginType));
Теперь можно приступить к созданию нового домена, загрузить в него все необходимое и запустить наш плагин на выполнение
AppDomain pluginDomain = AppDomain.CreateDomain(dir.Name + " plugin");
AssemblyHelper helper = (AssemblyHelper)pluginDomain.CreateInstanceAndUnwrap("PluginFramework", "MyPluginSample.Framework.AssemblyHelper");
helper.SearchFolder = dir.FullName;
Plugin plugin = (Plugin)pluginDomain.CreateInstanceAndUnwrap(reflectionAsm.FullName, typePlugin.FullName);
plugin.Initialize();
}
Вот и все! И напишем тестовый плагин для проверки
public class MyPlugin1: Plugin
{
public override void Initialize()
{
Console.WriteLine("Executing in Plugin 1. Domain Id: {0}", AppDomain.CurrentDomain.Id);
}
}
Структура каталогов следующая:

Ссылка на исходники PluginSystem.zip
Оставить комментарий
Комментарии


Сложной системе необходима структура (класс) описывающая плагин, его методы, входные и выходные данные. Это не просто реализовать, хотя вполне возможно.
