Страницы

Поиск по вопросам

пятница, 5 июля 2019 г.

Система плагинов

Захотелось научиться работать с отражением и вот этим всем. Начал с простенького, решил написать подключаемый плагин, который автоматически подгрузится из именной папки.
Что бы не мучиться с отражением методов и прочей нечистью на ум конечно же приходит наследование и полиморфизм. То есть мне нужен какой-то базовый класс с начальным функционалом от которого будут наследоваться все другие плагины.
То есть нужно сделать отдельную библиотеку с апи, которую будут использовать обе стороны (программа и плагин).
public interface IPlugin { void Loaded(); }
Вот такой класс придумал в этой библиотеке. Теперь я могу спокойно ее подключать к плагину и наследоваться.
public class Test : IPlugin { #region Implementation of IPlugin
public void Loaded() => Console.WriteLine( "Hello World" );
#endregion }
Вот у меня уже две библиотеки, одна с моим апи, другая его использует. Теперь мне нужно как-то загрузить сборку с плагином и использовать его методы. Вот тут мне и нужна помощь.
Так как библиотека плагина использует другую библиотеку с апи, я не смогу ее так просто загрузить, так как выскочит ошибка мол либе не хватает референсов. Как правильно загрузить плагин указав что сборка которая ему нужна уже загружена в программу?
Вот код которым я пытаюсь что-то наколдовать:
private static void Main ( string[] args ) { var pluginsPath = $"{Directory.GetCurrentDirectory()}\\Plugins"; var files = Directory.GetFiles( pluginsPath , "*.dll" );
foreach ( var file in files ) { Console.WriteLine( $"Trying load: {file}" ); Assembly assembly;
try { assembly = Assembly.Load( file ); } catch ( Exception ex ) { Console.WriteLine( ex.Message ); continue; }
var types = assembly.GetTypes().Where( type => type.IsClass && type.GetInterface( nameof( IPlugin ) ) != null );
foreach ( var type in types ) { var plugin = Activator.CreateInstance( type ) as IPlugin;
if ( plugin == null ) { Console.WriteLine( "Null" ); continue; }
plugin.Loaded(); } } }
Падает еще на первом try с сообщением:
Необработанное исключение типа "System.IO.FileLoadException" в mscorlib.dll Дополнительные сведения: Не удалось загрузить файл или сборку "C:\Users\anweledig\Documents\Visual Studio 2015\Projects\Anweledig\ConsoleApplication\bin\Debug\Plugins\TestPlugin.dll" либо одну из их зависимостей. Данное имя сборки или база кода недействительны. (Исключение из HRESULT: 0x80131047)


Ответ

Вы можете просто загрузить другую библиотеку. Все ее зависимости будут автоматически загружены.
Проблема может быть только в случае, если библиотека лежит не в той же папке, в которой лежит ваше приложение, и вы при этом загружаете ее через Assembly.LoadFrom. Чтобы это обойти, вам нужно указать рантайму что зависимости надо искать в папке плагина, а не в папке вашего приложения. Тогда можно будет использовать обычный Assembly.Load и заодно заработает станадартный механизм загрузки сборок.
Для этого можно использовать создание аппдомена с указанием AppDomainSetup.ApplicationBase
Т.е. схема примерно такая:
У вас есть базовая dll для плагинов - MyApp.SDK, в ней базовый класс (а лучше - интерфейс):
public abstract class Plugin { public abstract void Loaded(); public abstract void Unload(); }
в ней же код загрузки и вызова плагина (для простоты)
public static class PluginInvoker { public static void InvokePlugin() { var pluginAssembly = Assembly.Load("SomePlugin"); var pluginType = pluginAssembly.GetTypes().Where(t => typeof(Plugin).IsAssignableFrom(t)).Single(); Plugin plugin = (Plugin)pluginType.GetConstructor(new Type[0]).Invoke(null); plugin.Loaded(); } }
есть реализация этого плагина в SomePlugin.dll:
public class Test: MyApp.SDK.Plugin { public override void Loaded() { Console.WriteLine("Loaded"); }
public override void Unload() { Console.WriteLine("Unloaded"); } }
в SomePlugin есть референс на MyApp.SDK.
И есть главное приложение, в котором есть ссылка на MyApp.SDK, но нет ссылки на SomePlugin:
static void Main(string[] args) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = @"C:\Projects\MyApp\SomePlugin\bin\Debug\";
AppDomain pluginDomain = AppDomain.CreateDomain("plugin", null, setup); pluginDomain.DoCallBack(PluginInvoker.InvokePlugin); }
ApplicationBase задает папку, откуда AppDomain будет загружать сборки - т.к. это папка плагина, то все, на что он ссылается, будет корректно загружено.
Если плагины подгружаются из подпапки приложения (например, из Plugins), то в вместо AppDomainSetup.ApplicationBase можно использовать AppDomainSetup.PrivateBinPath
Если же при этом плагины вообще не планируется выгружать, и все плагины лежат в одной папке (без подпапок на каждый из плагинов) - то можно обойтись без отдельного AppDomain, просто дописав путь к папке с плагинами в секцию app.config/
код примера на github: https://github.com/PashaPash/PluginLoadSample

Комментариев нет:

Отправить комментарий