Пример системы плагинов
Придумал я себе такую задачу, как разработать к программе систему плагинов (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
Оставить комментарий
Комментарии
Сложной системе необходима структура (класс) описывающая плагин, его методы, входные и выходные данные. Это не просто реализовать, хотя вполне возможно.