Захотелось научиться работать с отражением и вот этим всем.
Начал с простенького, решил написать подключаемый плагин, который автоматически подгрузится из именной папки.
Что бы не мучиться с отражением методов и прочей нечистью на ум конечно же приходит наследование и полиморфизм.
То есть мне нужен какой-то базовый класс с начальным функционалом от которого будут наследоваться все другие плагины.
То есть нужно сделать отдельную библиотеку с апи, которую будут использовать обе стороны (программа и плагин).
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
Комментариев нет:
Отправить комментарий