![]() |
|
Модераторы: Partizan, gambit |
![]()
|
|
| Necias |
|
||||
|
Новичок Профиль Группа: Участник Сообщений: 45 Регистрация: 7.4.2006 Репутация: 2 Всего: 2 |
Многопоточный конструктор плагинов
на C#. Меня всегда поражала гибкость, которую предоставляет программе хороший конструктор плагинов. Наиболее известным примером, ИМХО, является Miranda IM – фантастический пример программы, полностью являющейся конструктором. Опираясь на исходный код этого мультипротокольного клиента я попытался разработать простенький многопоточный движок на совремнном – но таком высокоуровневом - языке C# (пользуясь Visual Studio 2005, и, соответственно, .NET Framework 2.0). Поскольку эта задача является очень комплексной и конечный код больше напоминает змея Уробороса – то есть не имеет ни конца, ни начала, – придется выкладывать огромные куски хорошо комментированного кода. К сожалению мои скромные преподавательские способности не позволяют объяснить эту тему совершенно неподготовленному человеку. Рекомендую осваивать эту статью, разобравшись с отражениями, потоками, а также архитектурой любой – самой-самой-самой простенькой операционки ибо конструктор плагинов, по сути аналогичен ОС. Определимся с понятием плагина – это просто dll-библиотека, содержащая класс с заранее заданным именем и несколькими стандартными функциями, способный обрабатывать возникающие события и (не обязательно) поддерживающий некоторые сервисы. Сервис – это просто такая функция, соответствующая некоторому шаблону и вызываемая по мере необходимости (аналогией сервиса в Windows являются WinAPI функции. Продолжая аналогию, событиям в нашем конструкторе синонимичны… события в windows)))) Начнем с конструктора. Этот класс во многом опирается на класс PluginClass, в котором, собственно, и происходит самое интересное, однако здесь осуществляются важные функции – добавляются в очередь событий новые события, вызываются сервисы
Поговорим о классе Plugin, который осуществляет работу непосредственно с некоторым dll-файлом плагина. Что нам нужно, чтобы работать с плагином? Во-первых его надо загрузить. Для загрузки плагина, являющегося dll-сборкой, достаточно воспользоваться статической функцией класса System.Reflection.Assembly LoadFile в самом простом ее варианте, требующим в аргументе строку – путь к файлу сборки. Функция возвращает результат типа System.Reflection.Assembly – класс для работы с загруженной сборкой. Теперь нам каким-то образом необходимо извлечь тип, определенный в сборке. Поскольку тип этот должен во-первых иметь специальное имя, напрмиер, SPlug (т.е. мы сможем его найти!) и во-вторых должен определять несколько стандартных функций (чтобы организовать связь!). Воспользуемся тем, что мы можем получить из загруженной нами сборки объект ManifestModule – как ясно из названия, это модуль манифеста)). Соответственно поскольку у нас есть манифест, мы можем достать из него нужный нам тип! А когда мы достанем нужный тип мы можем создать экземпляр класса SPlug, а также получить из него потребные нам методы. Информация о методах содержится в класса MethodInfo, для вызова метода нужно вызвать функцию Invoke соответствующего экземпляра класса MethodInfo и передать ей параметры для метода (параметры просто записываются по порядку в массив object’ов) и экземпляр класса, в котором будет выполняться метод (если вы не поняли, не переживайте – в исходном коде все станет ясно). Затем мы обрабатываем события, находящиеся в очереди событий. Формирование очереди вы увидите при рассмотрении класса Constructor. Вот код для класса Plugin
Фуф! Вот собственно и все. Очень рекомендую протрэйсить пример – это чрезвычайно простенькая консольная программа, там же – пример плагина. Замечания: вся вышеописанная громоздкая конструкция не претендует на оптимальность (хотя я и старался), безопасность от неперехваченных исключений при полномасштабном использовании и т.д. Однако основная идея соответствует выбранной теме, а исполнение (более или менее) – духу .NET. Обратите внимание: полность безопасный код, никаких указателей! Использовались источники: Г. Шилдт, «полный справочник по C#», MSDN, идеи и приемы из исходного кода MirandaIM. (с) Necias aka Kergan aka Хабибуллин Т., [email protected] Это сообщение отредактировал(а) Necias - 19.7.2006, 20:41 Присоединённый файл ( Кол-во скачиваний: 6 )
Constructor.rar 23,18 Kb |
||||
|
|||||
| mr.DUDA |
|
|||
|
3D-маньяк ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 8244 Регистрация: 27.7.2003 Где: город-герой Минск Репутация: 110 Всего: 232 |
Necias, честно говоря что-то тут непонятно, а точнее непонятно, для чего это всё нужно, и почему должно быть нужно, если всё так непонятно зачем это всё нужно
Если нужно (тьфу!)... если необходимо расширить программу плагинами, то обычно (точнее не так)... то некоторые люди объявляют такую штуку как интерфейс, выносят туда плагиновские методы, свойства и события... Дальше просто делаем обычную DLL ака Class Library с одним или несколькими классами, реализующими наш интерфейс... Далее в основной программе перечисляют assembly с типами, реализующими данный интерфейс (ака загрузка плугинов)... Ну и наконец - главная прога подписывается на события и дёргает методы, приводящие плугины в действие. Всё, и не нужны нам флаги и прочие замечательные вещи -------------------- ![]() |
|||
|
||||
| Necias |
|
|||
|
Новичок Профиль Группа: Участник Сообщений: 45 Регистрация: 7.4.2006 Репутация: 2 Всего: 2 |
если говорить о наследовании интерфейсов - то да, это один из вариантов передачи информации о стандартных функциях. Но при этом не надо забывать, что интерфейс должен быть объявлен в сторонней библиотеке универсальных типов, иначе ничем воспользоваться не удастся по крайней мере при динамическом использовании - даже если интерфейс в плагине будет таким же как и в главной программе. Здесь я просто избежал введения дополнительной длл, работая с отражениями. "подписывается на события" в одном треде, да еще если события получает ТОЛЬКО "главная прога" это действительно элементарно, но тут мультитред + события возбуждаются кем угодно и без очереди событий будет у тебя нещастье (когда событий будет много, начнут пропадать). А как "дергать методы", но не из главной проги, а из плагина, использующего функционал другого плагина? При условии, что оба должны использовать один экземпляр класса. Так и появляется концепция сервисных функций (т.е. Ф-ий, не предусмотренных в интерфейсах, но которые созданы для использования другими плагинами). Замечание о наследовании интерфейсов верное, однако здесь, как уже было сказано, реализован другой подход - ручная загрузка методов (ненамного более трудоемкий), других же проблем это наследование, как видите, не решает. Следовало заранее сказать: это не просто конструктор, расширяющий функционал - это инструмент module-independent реализации приложения (в литературе я встречал термин "модулярный двигатель"). если, например, сравнивать конструктор и язык программирования, то обычные конструкторы - это процедурно-модульные языки, а модулярный - это уже объектно-ориентированный(заметьте, ориентированный, а не обоснованный). Например если мы хотим добавить работу с какой нить хитрой бд - добавляем модуль. Но это может и обычный конструктор. Здесь же этот модуль смогут использовать другие плагины, причем - напрямую. Например, плагин ОТЧЕТОВ. а дальше? Пишем плагин бизнес-анализа с хитрыми формулами и длинными алгоритмами. И она использует плагин отчетов. Угу?
|
|||
|
||||
| arilou |
|
||||
![]() Великий МунаБудвин ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 2646 Регистрация: 15.7.2004 Где: город-герой Минск Репутация: 21 Всего: 61 |
Necias, рекомендую почитать вот тут:
http://forum.vingrad.ru/index.php?showtopic=37869 Добавлено @ 23:12 Necias, Для чего создавать универсальный механизм плагинов? В каждой программе свои потребности, а one-size-fits-all - это не самый лучший выход.
При разработке плагина, которому требуется функционал другого плагина вы выставляете reference на сборку, в которой лежат нужные вам классы, и обращаетесь к основному приложению, чтобы оно вернуло вам инстанс нужного плагина и ипользуете его. Вот и вся любовь P.S.
упростили немного ситуацию |
||||
|
|||||
| Necias |
|
|||
|
Новичок Профиль Группа: Участник Сообщений: 45 Регистрация: 7.4.2006 Репутация: 2 Всего: 2 |
а почему бы не сделать универсальным и "ковергентным" то, что можно сделать таковым? За счет функции CallService это элементарно и нет разницы, используется ли функция из интерфейса или она вызывается как сервис.
По поводу вставки референции и получении инстанса - каюсь, не подумал |
|||
|
||||
| arilou |
|
|||
![]() Великий МунаБудвин ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 2646 Регистрация: 15.7.2004 Где: город-герой Минск Репутация: 21 Всего: 61 |
Это будет позднее связывание, и ошибки, которые вполне могли быть проверены на этапе компиляции, будут перенесены в рантайм, что не есть хорошо. |
|||
|
||||
| mr.DUDA |
|
|||
|
3D-маньяк ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 8244 Регистрация: 27.7.2003 Где: город-герой Минск Репутация: 110 Всего: 232 |
Наверное, это дело пристрастия к определённому стилю проектирования. Кому-то нравится так, кому-то сяк, а кое-кто сделает наперекосяк... Ну это лирика. По теме хочется сказать, что модульное проектирование в приведённой трактовке ничем не отличается от компонентно-ориентированного, те же eggs только сбоку.
Заводя ещё одну длл с описанием интерфейсов, мы ничего не потеряем, а если так уж не хочется - пожалуйста, добавляйте в плагин референс на основную программу, где описан интерфейс. По поводу многопоточности конечно сложно что-то с уверенностью сказать. Единственное, это Куда это будут события "пропадать" ? Генерация события C# суть обычный вызов метода. В том же потоке. Если и будут проблемы с многопоточностью - то в другой плоскости, когда понадобится доступ к одним ресурсам из нескольких потоков, появятся lock-и и т.п. -------------------- ![]() |
|||
|
||||
| Necias |
|
|||
|
Новичок Профиль Группа: Участник Сообщений: 45 Регистрация: 7.4.2006 Репутация: 2 Всего: 2 |
Сейчас объясню, про очередь. Допустим ее у нас нет и мы сразу, из функции raiseevent вызываем нужный метод. А что это значит? Это значит, что мы должны войти в поток каждого плагина иначе вызванный метод будет работать в главном потоке и вызвать нужный метод. При этом основной поток должен оставаться в функции raiseevent, тоесть мы должны ждать, когда обработается событие. Такие вхождения обходятся крайне дорого по времени, а при неграмотной реализации семафоров есть риск потери событий некоторыми потоками. Когда этот конструктор в версии без очереди событий тестировался на эффексть, тормоза были жуткие и от мультитреда - одно название. Очередь повысила производительность весьма ощутимо.
|
|||
|
||||
![]()
|
| Прежде чем создать тему, посмотрите сюда: | |
|
|
Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов. Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :) Так же не забывайте отмечать свой вопрос решенным, если он таковым является :) Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle. |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |