Нужно сделать следующее: есть приложение, где форма оформленна для MDI. Есть директория, кде находятся сборки. Приложения должно просматривать всю директорию и загружать находящиеся там сборки. Сборки должны добавлять на главную форму свои пункты меню, и кнопки на панель инструментов.И дальше уже при выборе нужных пунктов меню, должны вызывать функции и показываться формы находящиеся в сборках.
Как это реализовать?
Скажите, в какую сторону копать?
Здравствуйте, Аноним, Вы писали:
А>Нужно сделать следующее: есть приложение, где форма оформленна для MDI. Есть директория, кде находятся сборки. Приложения должно просматривать всю директорию и загружать находящиеся там сборки. Сборки должны добавлять на главную форму свои пункты меню, и кнопки на панель инструментов.И дальше уже при выборе нужных пунктов меню, должны вызывать функции и показываться формы находящиеся в сборках.
А>Как это реализовать? А>Скажите, в какую сторону копать?
Подключаем сборки через Reflection
1. Загружаем сборку
/// <summary>
/// Загружает из заданной сборки заданный тип
/// </summary>
/// <param name="RequiredType">Имя файла сборки без указания пути</param>
/// <param name="ConstructorArguments">Имя файла сборки без указания пути</param>
/// <param name="AssemblyFileName">Имя файла сборки без указания пути</param> public static object LoadInstanceOf(Type RequiredType, object[] ConstructorArguments, string AssemblyFileName)
{
Assembly ass = GetAssembly(AssemblyFileName); if(ass==null) return null;
try
{
//ищем входной объект плагинаforeach (Type type in ass.GetTypes())
{
if(type.BaseType == RequiredType)
{
//Создаем экземпляр классаreturn Activator.CreateInstance(type,ConstructorArguments);
}
}
return null;
}
catch (Exception ex) //перехватываем исключения в плагинах
{
.......
}
return null;
}
#endregion
Теперь по общим вопросам.
1. Я разделяю в плагине сборки конфигурацию (PluginConfiguration) и формы (PluginForm). Когда главная форма загружает необходимый плагин она загружает конфигурацию которая предоставляет информацию основной форме к примеру
и соответственно главная MDI форма выстраивает свои меню и тулбары в соответствии с данными полученными из PluginConfiguration.
2. Каждому пункту меню и кнопке тулбара в плагине соответствует некая PluginCommand (см паттерн Command). Она управляет доступностью элементов меню и кнопок в определенный момент а так же выполняет действие внутри плагина при нажатии пункта меню(кнопки тулбара)
В итоге как мне кажеться получается достаточно слабое связывание.
При добавлении дочерних форм PluginForm, PluginConfiguration посылает событие основной форме и та уже устанавливает себя в качестве родительской PluginForm
У меня похожая задача единственное отличие это то что эти плгины нужно выгружать,
то есть нужно их грузить в отдельный домен
но главная форма в одном домене а дочернее окно в другом работать не будут.
Что делать?
Здравствуйте, FlyDN, Вы писали:
FDN>Здравствуйте, Andrew Merkulov, Вы писали:
FDN>У меня похожая задача единственное отличие это то что эти плгины нужно выгружать, FDN>то есть нужно их грузить в отдельный домен FDN>но главная форма в одном домене а дочернее окно в другом работать не будут. FDN>Что делать?
а зачем выгружать? Просто убивать инстансы не пойдет?
имхо, вес хорошо. Вот только ход с PluginConfiguration не позволяет делать не persistent кнопки и т.п. Я юы сделал как в Office, те позволял бы плагину самому добавлять кнопки когда нужно.
Здравствуйте, Константин Л., Вы писали:
КЛ>а зачем выгружать? Просто убивать инстансы не пойдет?
К сожалению нет.
Эти модули будут компилироваться и загружаться во время работы приложения,
изменяться и опять компилироваться и загружаться.
Если их не выгружать то рано или поздно их будет загружено очень много.
Здравствуйте, FlyDN, Вы писали:
FDN>Здравствуйте, Константин Л., Вы писали:
КЛ>>а зачем выгружать? Просто убивать инстансы не пойдет?
FDN>К сожалению нет. FDN>Эти модули будут компилироваться и загружаться во время работы приложения, FDN>изменяться и опять компилироваться и загружаться. FDN>Если их не выгружать то рано или поздно их будет загружено очень много.
а разбить плагин на 2 сборки, одна из которых будет показывать UI и не выгружаться?
AA>Поскольку если сборка с типом еще не загружена, то экземпляр Type RequiredType описывающий тип из этой сборки взять будет просто неоткуда.
Если бы тип был описан внутри сборки плагина то да. Но если вы посмотрите внимательно в коде:
if(type.BaseType == RequiredType)
Типы прописанные внутри плагина сравниваются с неким базовым типом, который прописан не в плагине. RequiredType — это и есть базовая конфигурация, а все конкретные конфигурации наследдуются от неё и живут в сборках-плагинах.
условная программа "Главное MDI окно" знает только о базовой конфигурации, ничего не зная о типах в плагине.
Здравствуйте, Константин Л., Вы писали:
КЛ>Здравствуйте, Andrew Merkulov, Вы писали:
КЛ>имхо, вес хорошо. Вот только ход с PluginConfiguration не позволяет делать не persistent кнопки и т.п. Я юы сделал как в Office, те позволял бы плагину самому добавлять кнопки когда нужно.
Поясните пожалуйста, что такое "persistent кнопки"? Наверное имеется в виду те, которые появляются при создании дочернего окна к примеру?
С кнопками скорее всего так и есть для Net 1.1. Т.к. Winforms не позволяют Merge-ить тулбары как к примеру менюшки дочерних и главной формы.
Думается мне что это проблема решена в Net 2?
Решение: можно ввести дополнительный интерфейс и при создании дочернего окна добавлять к главному тулбары, запрашивая их у PluginConfiguration, которая будет брать их у конкретного создаваемого окна, являясь в свою очередь фасадом для главной формы.
Здравствуйте, FlyDN, Вы писали:
FDN>Здравствуйте, Andrew Merkulov, Вы писали:
FDN>У меня похожая задача единственное отличие это то что эти плгины нужно выгружать, FDN>то есть нужно их грузить в отдельный домен FDN>но главная форма в одном домене а дочернее окно в другом работать не будут. FDN>Что делать?
По поводу отдельного домена вы правы. Т.к сборку отдельно выгрузить нельзя, только домен.
А в чем замес ? почему не работают в различных доменах?
Здравствуйте, Andrew Merkulov, Вы писали:
AM>Здравствуйте, Константин Л., Вы писали:
КЛ>>Здравствуйте, Andrew Merkulov, Вы писали:
КЛ>>имхо, вес хорошо. Вот только ход с PluginConfiguration не позволяет делать не persistent кнопки и т.п. Я юы сделал как в Office, те позволял бы плагину самому добавлять кнопки когда нужно.
AM>Поясните пожалуйста, что такое "persistent кнопки"? Наверное имеется в виду те, которые появляются при создании дочернего окна к примеру? AM>С кнопками скорее всего так и есть для Net 1.1. Т.к. Winforms не позволяют Merge-ить тулбары как к примеру менюшки дочерних и главной формы. AM>Думается мне что это проблема решена в Net 2?
честно, не знаю. У меня практически нет опыта написания WinForms аппликэйшенов. Я имел ввиду, что плагин сам решает, когда и куда что добавить. В твоем же случае это делается самим приложением и при загрузке. Те плагин не может а рантайме добавлять и удалять кнопки.
AM>Решение: можно ввести дополнительный интерфейс и при создании дочернего окна добавлять к главному тулбары, запрашивая их у PluginConfiguration, которая будет брать их у конкретного создаваемого окна, являясь в свою очередь фасадом для главной формы.
Здравствуйте, Константин Л., Вы писали:
КЛ>честно, не знаю. У меня практически нет опыта написания WinForms аппликэйшенов. Я имел ввиду, что плагин сам решает, когда и куда что добавить. В твоем же случае это делается самим приложением и при загрузке. Те плагин не может а рантайме добавлять и удалять кнопки.
В моем случае это и не нужно. Если понадобится когда-нибудь я не вижу проблемы в том чтобы встроить данную функциональность в существующую реализацию. Можно например воспользоватся эвентами . К примеру
заменить методы
Я не хочу чтобы плагин видел MainForm.MainMenu и MainForm.Toolbar (или различные вариации). Вариант со слабым связыванием мне нравиться больше. Для офиса такое поведение не подходит т.к. документы и их VBA программы вовсю используют объектную модель офиса, а у меня плагины разнородны и не пользуют единый Workflow. "Главное MDI окно" (Консоль приложений) является лишь оболочкой для запуска плагинов, предоставляя им некий общий функционал: подключение к БД, права пользователя, механизм обмена сообщениями, хранение настроек и т.п.
Что в принципе красиво и в будущем возможно я пересмотрю их взаимодействие в сторону расширения паттерна Command, добавлю ему к существующим свойства Enabled,Cheked, Name, свойство Icon и буду созданные в плагине команды добавлять по аналогии с офисом:
Я загрузку делаю так. Классы, которые экспортируются плагином должны реализовывать некоторый интерфейс IPlugin. Чтобы узнать, что данный класс нужно загрузить, он должен быть помечен некоторым ненаследуемым аттрибутом PluginAttribute. Казалось бы, достаточно и того, что класс реализует IPlugin, но, мне кажется, с атрибутом удобнее, это позволит внутри сборки с плагином реализовавывать, например, иерархию, когда уже базовый класс реализует IPlugin, а экспортировать нужно только производный. В общем, код загрузки у меня выглядит так:
public void LoadPlugins(string pluginsPath)
{
const string searchPattern = "*.dll";
try
{
string[] files = Directory.GetFiles(pluginsPath, searchPattern, SearchOption.AllDirectories);
List<Assembly> assemblies = new List<Assembly>(10);
Assembly assembly;
foreach (string file in files)
{
try
{
assembly = Assembly.LoadFrom(file);
assemblies.Add(assembly);
}
catch (BadImageFormatException exception)
{
///TODO: Запись в лог, сообщение администратору и т.п.
///о том, что файл не является валидной сборкой
}
catch (FileLoadException exception)
{
///TODO: Запись в лог, сообщение администратору и т.п.
///об ошибке загрузки файла
}
catch (SecurityException exception)
{
///TODO: Запись в лог, сообщение администратору и т.п.
}
}
foreach (Assembly a in assemblies)
{
Type[] types = a.GetExportedTypes();
foreach (Type type in types)
{
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract &&
type.IsDefined(typeof(PluginAttribute), false))
{
try
{
_plugins.Add(Activator.CreateInstance(type) as IPlugin);
}
catch (MissingMethodException exception)
{
///TODO: у плагина нет конструктора без параметров.
///Запись в лог, сообщение администратору и т.п.
}
}
}
}
}
catch (DirectoryNotFoundException exception)
{
///TODO: Запись в лог, сообщение администратору и т.п.
///о том, что директория не найдена
}
}
Может кому не нравится такой подход? Тогда жду критики.
Здравствуйте, Alexander_Demchenko, Вы писали:
A_D>Может кому не нравится такой подход? Тогда жду критики.
Ты ближе всех подошел к самому кошерному решению..
Большие дядьки пользуются для таких вещей паттерном IServiceProvider, AVK как раз рассказывал об этом на прошлой встрече RSDN User Group.
Здравствуйте, Andrew Merkulov, Вы писали:
AM>А в чем замес ? почему не работают в различных доменах?
При попытке добавить форме контрол из другого домена получаем RemotingException
"Remoting cannot find field 'parent' on type 'System.Windows.Forms.Control'."
Здравствуйте, FlyDN, Вы писали:
FDN>Здравствуйте, Andrew Merkulov, Вы писали:
AM>>А в чем замес ? почему не работают в различных доменах?
FDN>При попытке добавить форме контрол из другого домена получаем RemotingException FDN>"Remoting cannot find field 'parent' on type 'System.Windows.Forms.Control'."
Да действительно не работает . Если найдете решение опубликуйте его. Интересно как обойти эти грабли. С одной стороны нужно выгружать домен а с другой стороны организовать взаимодействие между формами различных доменов