Страницы

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

суббота, 11 апреля 2020 г.

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

#c_sharp #plugin #рефлексия

                    
Захотелось научиться работать с отражением и вот этим всем.
Начал с простенького, решил написать подключаемый плагин, который автоматически подгрузится
из именной папки.

Что бы не мучиться с отражением методов и прочей нечистью на ум конечно же приходит
наследование и полиморфизм.
То есть мне нужен какой-то базовый класс с начальным функционалом от которого будут
наследоваться все другие плагины.

То есть нужно сделать отдельную библиотеку с апи, которую будут использовать обе
стороны (программа и плагин).

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)

    


Ответы

Ответ 1



Вы можете просто загрузить другую библиотеку. Все ее зависимости будут автоматически загружены. Проблема может быть только в случае, если библиотека лежит не в той же папке, в которой лежит ваше приложение, и вы при этом загружаете ее через 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

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

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