Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > C/C++: Общие вопросы > Проанализируйте интерфейс системы плагинов |
Автор: ZVano 16.9.2011, 11:46 | ||||||||||||||||
Пишу движек плагинистого приложения. Он уже работает, но гложут смутные сомнения - не напортачил ли я при проектировании... Нужна оценка профессионалов, которые уже сталкивались с подобной задачей. Идеология такова:
Во главе системы стоит заголовочный файл "ZIMPluginInterfaces.h", который описывает базовые интерфейсы плагинов и менеджера плагинов. Менеджер плагинов - объект из DLL, который является наследником "IPluginsManager" либо "IPluginsManager_1"либо "IPluginsManager_2" и т.д. Плагин - объект из DLL, который является наследником "IPlugin" либо "IPlugin_1"либо "IPlugin_2" и т.д. Содержимое "ZIMPluginInterfaces.h":
Менеджер плагинов в DLL "ZIMPluginsManager.dll"
Заголовок реализации внутри "ZIMPluginsManager.dll"
Программа-оболочка, которая хочет использовать систему плагинов обязана выполнить код, подобный следующему:
Ну и пример плагина "PluginShell". "PluginDriveSchoolDb.cpp" - главный файл плагина "PluginShell". Объявлена точка входа в DLL "DllEntryPoint" и функция получения интерфейса плагина "GetPluginInterface".
"TPluginShell.H" - заголовочный файл реализации плагина. Доступен только внутри плагина "TPluginShell". Реализовывает публичный интерфейс "IPluginShell_1", который объявлен в публичном заголовочном файле "IPluginShell.h"
"IPluginShell.h" - публичній заголовочній файл плагина "PluginShell". Все другие плагины, которые хотят использовать "PluginShell", обязаны заинклудить "IPluginShell.h"
PS: Если найдутся желающие поковырять код - пишите. Выложу весь проект и напишу пару тестовых плагинов. |
Автор: borisbn 16.9.2011, 18:09 | ||||
1. Интерфейсам нужно явно указать выравнивание.
2. Всем функциям нужно указать явный тип вызова
3. Зачем в IPlugin все переменные в public ? Они реально все нужны в основной программе ? 4. Я бы добавил ф-цию получения displayName плагина. Т.е. Система работает с плагином по pluginName, а сообщения пользователю выдаёт из displayName. Ну, это так... на первый взгляд |
Автор: mes 16.9.2011, 20:13 |
чтоб например в меню выбирать, или отключать.. |
Автор: newbee 16.9.2011, 21:19 |
Здравствуйте, мои дорогие любители избушек на курьих ножках. Раньше я тоже была поглощена идеей поработить мир написать убер систему плагинов и пришла к следующему выводу: на С++ сделать одновременно и универсальную, и удобную систему не получится. Что значит универсально? Это возможность плагином внесения любых (читай максимально широких) изменений в ход работы программы и других плагинов. Удобно - значит не париться с магическими идентификаторами плагинов и кодами действий при посылании сообщений другим участникам системы, в идеале - получение полного C++-API любой подсистемы из любой другой. Таскать за каждым плагином заголовочные файлы - не вариант: система из уже полусотни компонент, особенно разных авторов, будет едва ли подъемной, а сопровождать ее и обновлять часть ее подсистем станет просто нереально, а встанет задача поддержки плагином нескольких версий другого - ващще жопа. В рантайме экспортировать все необходимые функции по одной - уже несколько проще, но опять же трудоемко, прощай ООП-интерфейс, прощай name mangling, привет геммор с выяснением типов и количеством аргументов (и привет, бусткодер). Намного проще расширять программу с помощью встроенного скриптового языка, для краткости назовем его lisp. Из основной программы даем лиспу доступ ко всему необходимому, далее наращиваем функционал системы уже на лиспе, на нем же реализуем систему плагинов, благодаря динамичной природе которого лишенную недостатка строгого следования API и ABI между подсистемами. И на нем же пишем плагины, а какие-то критичные части всегда можно вынести в код на С++. Плюсы очевидны: писать на лиспе намного проще и быстрее, чем на С++; подсистемы общаются между собой на нормальном языке; а не каком-то жалком обрубке вроде системы сообщений; большинство плагинов будут написаны на одном только лиспе, а значит мы получаем офигенскую кроссплатформенность. Все на правах ИМХи и ващще мне уже пора бежать. Чао! |
Автор: borisbn 17.9.2011, 09:40 | ||||||||
Смотри... ты даёшь мне эти h-ники, чтобы я сделал како-то плагин. У тебя в проекте главного приложения (билдер, как я понимаю) по-умолчанию выставлено выравнивание структур на 8 байт. Я создаю проект для плагина в студии, и у меня выравнивание 4 байта. Интерфейсы, которые я создам, на бинарном уровне не будут соответствовать тем, которые ты ожидаешь. Эпик фэйл.
![]() Абсолютно то же, что и по п. 1
Скорее наоборот, создатель плагина не может быть уверен, что заполненные им переменные не изменятся от вызова к вызову, что основная программа не будет их менять. почему не const char *actionName ? Теперь следующее - ты достаточно широко проработал систему подключения плагинов, но обычно плагины должны выполнять какие-то действия над данными, имеющимися в основной программе. Например в фотошопе плагинам передаётся редактируемая картинка, в браузерах - HTML, в плеерах - видеопоток и т.д. У тебя же я этого не нашёл. И ещё. Из своего опыта. Плагинам иногда необходимо сохранять своё состояние или считывать некие настройки. Они это могут делать сами (тупо открывать ini-шник с заранее известным именем и читать/писать его), но может создаться ситуация, когда потребуется подключить 2 экземпляра одного и того же плагина или 2 разных плагина решат назвать ini-шник config.ini.... Чтобы избежать этого необходимо, чтобы основная программа предоставляла плагинам интерфейс сохранения/считывания настроек. Типа такого
Добавлено через 9 минут и 49 секунд И ещё. Но это уже не относится к системе, а скорее к правилу хорошего тона (что тоже не маловажно). Обычно принято public-члены классов объявлять в начале описания класса, а private (или protected) - в конце. Людям, которым ты отдаёшь класс для использования, не интересно какие защищённые переменные есть в классе, им интересно как им использовать этот класс, т.е. public-члены. |
Автор: ZVano 19.9.2011, 11:17 | ||||||||||||||||||
Благодарю за пояснения. Мое упущение. Надо будет добавить const всем константным параметрам, пока не плагинов немного.
Плагины взаимодействуют друг с другом напрямую через метод IPlugin_MessageParse.
Т.е. применительно к фотошопу плагин, обрабатывающий картинку, обязан: 1. быть версии "IPlugin_2" или выше 2. содержать объявление наследника от TPluginMessage, который содержит все необходимые поля для выполнения действия (либо осуществления коммуникации, если подразумевается обратная связь). Пример ниже. 3. Реализовать в методе "MessageParse" распознавание кода fMsgCode и передаци управления функции, которая обслуживает сообщения данного типа. Напомню интефейс базового сообщения класса TPluginMessage. Наследник обязан назначить свой код "DWORD fMsgCode". Код уникален в пределах плагина, который обслуживает сообщение.
Пример реализации коммуникации между плагином IPhotoshopEditor - редактор изображения (формочка с рисунком) и IEffectDegradation - эффект размытия изображения. Загрузка программы: 1. Оболочка грузит все плагины, зарегестрированые в секции "Автозагрузка" конфигурационного файла. >>Среди них плагин IEffectDegradation. 1.1. Дошла очередь до загрузки IEffectDegradation. 1.1.1. Оболочка загружает DLL с плагином, и получает интерфейс IEffectDegradation вызвав функцию DWORD GetPluginInterface(IPlugin ** out_Plugin); из DLL. 1.1.2. Оболочка приводит "IPlugin ** out_Plugin" к IPlugin_2 (т.к. out_Plugin->IPluginVersion == 2) 1.1.3. Оболочка вызывает метод IEffectDegradation->"virtual DWORD IPlugin_Init(void) = 0;", который достался IPlugin_2 от IPlugin_1. >>Выполняется инициализация IEffectDegradation
После инизиализации плагинов главное меню оболочки будет содержать пункты меню всех плагинов автозагрузки, которые поддерживают работу с оболочкой и попросили ее (оболочку) зарегистрировать свое меню. 2. Работа приложения. 2.1. Пользователь клоцает мышей по пункту меню. Допустим, это будет пункт "Загрузить изображение", который принадлежит плагину IPhotoshopEditor. 2.1.1. Оболочка ищет ассоциацию "Пункт меню" == "Плагин\Действие". 2.1.2. Оболочка вызывает метод IPhotoshopEditor->IPlugin_DoAction("Название действия, загруженное ранее из конфигурационного файла"); 2.1.2.1. Плагин IPhotoshopEditor показывает диалог выбора файлов 2.1.2.2. Плагин IPhotoshopEditor создает новое окно редактора и открывает выбранный пользователем файл. 2.1.2. Возврат управления плагину "Оболочка". 2.2. Пользователь клоцает мышей по пункту "Применить эффект "Размытие"". 2.2.1. Оболочка ассоциирует пункт меню с плагином "IEffectDegradation", действием "Размыть изображение". 2.2.2. Оболочка вызывает метод IEffectDegradation->IPlugin_DoAction("Размыть изображение"); 2.2.3. Плагин IEffectDegradation выполняет действие. 2.2.3.1. IEffectDegradation запрашивает у плагина "Оболочка" интерфейс плагина, которому принадлежит текущее активное окно (пусть интерфейс будет в переменной IPlugin * iPluginActive). 2.2.3.2. IEffectDegradation проверяет, умеет ли он работать с данным плагином. Естественно, с IPhotoshopEditor он умеет работать т.к. он писалься для него. 2.2.3.3. IEffectDegradation приводит интерфейс iPluginActive к IPhotoshopEditor нужной версии. 2.2.3.4. IEffectDegradation создает переменную структуры TPhotoshopEditor_MsgGetImage << TPluginMessage, которая объявлена в публичном файле плагина IPhotoshopEditor. "IPhotoshopEditor.h"
Пусть переменная будет TPhotoshopEditor_MsgGetImage msgGetImage; 2.2.3.5. IEffectDegradation вызывает метод IPhotoshopEditor->IPlugin_MessageParse(&(TPluginMessage *)msgGetImage) 2.2.3.5.1. IPhotoshopEditor читает fMsgCode переданного сообщения. Он равен 13. 2.2.3.5.2. IPhotoshopEditor приводит TPluginMessage * message к TPhotoshopEditor_MsgGetImage. 2.2.3.5.3. IPhotoshopEditor записывает в fImage адрес изображения, которое он редактирует. И возвращает управление. 2.2.3.6. IEffectDegradation выполняет размытие изображения, адрес которого получит из msgGetImage->fImage Финита. Работа выполнена - плагины состыковались, изображение размыто. Добавлено через 2 минуты и 57 секунд
Может быть и так, но я попробую ![]() Добавлено через 4 минуты и 6 секунд
Согласен на 100%. Прийму к сведению. Добавлено через 8 минут и 25 секунд
config.ini будет для каждого плагина свой и пути будут относительно папки с файлом DLL плагина. Поэтому пересечение исключено. Настройки будут через объект класса IOpteoned, который будет содержать методы (Load, Save, Cancel) и форму с настройками для включения в ее как закладки в плагин "МенеджерНастроек". Но это пока только в планах. |
Автор: xvr 19.9.2011, 15:09 |
Ваша система плагинов очень напоминает попытку создать с нуля COM. Видимо следующей стадией будут библиотеки типов и ActiveX ![]() Может не стоит изобретать велосипед, а воспользоваться готовым? |
Автор: ZVano 19.9.2011, 15:35 |
Честно говоря, даже не подозревал об этом ![]() С удовольствием. Вот только не нашел я готовых движков. http://forum.vingrad.ru/forum/topic-327000.html спрашивал форумчан. |
Автор: borisbn 19.9.2011, 15:44 |
За исключением одного маленького плюсика (весьма сомнительного, к слову) - кроссплатформенная. |
Автор: ZVano 19.9.2011, 15:54 | ||
Кстати, именно кроссплатформенности и хотелось добиться. Но знаний для этого у меня не хватает. Есть мысль замутить опенсорсный движек и разместить его на googlecode.com |
Автор: newbee 19.9.2011, 16:05 |
ZVano, тогда быстро, решительно выбрасывай дебилдер и начинай компилировать Gcc! Если гуевая часть заявляется в базовом функционале (извини, я мельком читала твои простыни), используй Qt. |
Автор: borisbn 19.9.2011, 16:10 |
Что это за HMODULE, DWORD, MAX_PATH в интерфейсе ? +100500. ещё +100500. итого: +2001000 ![]() |
Автор: xvr 19.9.2011, 16:15 | ||
Тогда XPCOM из Mozilla (FireFox) Собственно ActiveX и есть ваш готовый движок (ну почти) |
Автор: ZVano 19.9.2011, 16:33 | ||
Полностью выбросить не получится т.к. начальство не позволяет. Хотелось бы получить на выходе: 1. кроссплатформенные интерфейсы базового плагина и менеджера плагинов 2. экземпляр менеджера плагинов в dll А весь остальной функционал в реализациях плагинов на Builder, VS, и т.д. GUI в базовом функционале не заявляется. Движек GUI это плагин интерфейса IPluginShell. Все GUIшные плагины цепляются к нему. Это типы из WINDOWS.H, WINDEF.H ![]() На что ума хватило, то и написал (лучше что-то, чем ничего). Пытался разобраться в исходниках FireFox, но абсолютно ничего не понял. Здоровенные они там. |
Автор: xvr 19.9.2011, 16:48 |
Все исходники не нужны, XPCOM у них лежит отдельно. Да и по функциональности он должен повторять обычный COM (хотя не проверял) |
Автор: ZVano 20.9.2011, 17:43 |
Дополнение к техзаданию ![]() Кроссплатформенность не нужна. Достаточно иметь совместимость на уровне dll, которые созданы в разных средах программирования (ориентируюсь на C++ Builder, Visual Studio C++, Delphi). |
Автор: xvr 20.9.2011, 18:00 |
Тогда COM/ActiveX однозначно. |