Применение рефлексии для создания плагинов - Основное приложение

ОГЛАВЛЕНИЕ

 

Основное приложение

    Приложение MainApp, к которому мы будем подключать плагины, это простое windows-forms приложение для отображения графический файлов. Оно реализует интерфейс IMainApp - класс формы определен как public class Form1 : System.Windows.Forms.Form, Interface.IMainApp. На форме находится PictureBox для вывода изображения. Для реализации интерфейса IMainApp определяем свойство Image для доступа к изображению.

public Bitmap Image
{
    get { return (Bitmap)pictureBox.Image; }
    set { pictureBox.Image = value; }
}

    В конструкторе формы вызывается метод FindPlugins, который находит плагины в папке с приложением и загружает их сборки. Для поиска и загрузки применяется рефлексия. Существует и другой подход - создать для приложения конфигурационный файл, в котором прописаны пути ко всем плагинам. При этом мы не сможем устанавливать плагины путем простого копирования сборок, что не есть хорошо. 

void FindPlugins()
{
    // папка с плагинами
    string folder = System.AppDomain.CurrentDomain.BaseDirectory;

    // dll-файлы в этой папке
    string[] files = Directory.GetFiles(folder, "*.dll");

    foreach (string file in files)
        try
        {
            Assembly assembly = Assembly.LoadFile(file);

            foreach (Type type in assembly.GetTypes())
            {
                Type iface = type.GetInterface("Interface.IPlugin");

                if (iface != null)
                {
                    Interface.IPlugin plugin = (Interface.IPlugin)Activator.CreateInstance(type);
                    plugins.Add(plugin.Name, plugin);
                }
           }
      }
      catch (Exception ex)
      {
          MessageBox.Show("Ошибка загрузки плагина\n" + ex.Message);
      }
}

    Вначале определяется папка для поиска плагинов. Т.к. у нас все плагины лежат в одной папке вместе с основным приложением, то мы используем свойство BaseDirectory для домена нашего приложения. Затем получаем все dll файлы из папки - их массив возвращает статическая функция GetFiles. Сборку для проверки на наличие плагина загружаем методом LoadFile и в цикле проходим по всем типам, определенным в сборке. Если тип содержит интерфейс IPlugin (при этом метод GetInterface возвращает не null), то создаем экземпляр этого типа (инстанцируем) методом Activator.CreateInstance. Для последующего использования мы сохраняем инстанцированный тип в хеш-таблице plugins. Ключем в хеш-таблице является название плагина.
    Потенциальной проблемой для нашего кода может стать то, что из домена приложения нельзя выгрузить сборку. Если в папке с приложением окажется много сборок, которые будут загружаться в процессе поиска плагинов, то это приведет к ненужному расходу памяти. В таком случае можно создать новый домен, вызвав статическую функцию AppDomain.CreateDomain, загрузить все сборки в созданный домен и получить названия только тех сборок, которые содержат плагины, выгрузить домен функцией AppDomain(Unload) и загрузить сборки с плагинами в домен.
    После того, как все плагины найдены, создаем для них в функции CreatePluginsMenu пункты меню. Названия пунктов меню берутся из ключей в хеш-таблице. Для обработки событий от меню для вызова плагинов создается обработчик OnPluginClick. В обработчике определяется названия пункта меню, который выбрал пользователь, и по нему, как по ключу в хеш-таблице, получаем интерфейс IPlugin соответствующего плагина. У плагина вызывается метод Transform, в качестве параметра this (т.к. класс формы наследуется от интерфейса IMainApp).

void CreatePluginsMenu()
{
    // создаем обработчик для команд меню для плагинов
    EventHandler handler = new EventHandler(OnPluginClick);

    foreach (string name in plugins.Keys)
    {
        MenuItem item = new MenuItem(name, handler);
        menuItemPlugins.MenuItems.Add(item);
    }
}


private void OnPluginClick(object sender, EventArgs args)
{
    Interface.IPlugin plugin = (Interface.IPlugin)plugins[((MenuItem)sender).Text];
    plugin.Transform(this);
}

    Окончательно приложение с загруженной фотографией выглядит следующим образом. В пункте меню Фильтры добавлены два плагина.