Суть вопроса в том, что у нас в разработке уже около года идёт приложение на C++ (MSVC2015) с плагинами.
Идея, заложенная в систему плагинов, кратко изложена здесь: http://www.andynicholas.com/?p=27
В основу интерфейса плагина (aka абстрактный базовый класс в контексте C++) первоначельно был заложен такой вот код:
Эта версия — самая первая (старая).
Теперь интерфейс развивается — добавляются новые вирт-методы.
Я предлагаю их добавлять в самый конец — послеDeInit.
Это позволит использовать те плагины, которые компилировались под старой версией интерфейса.
Коллега по работе добавил новый метод SuspendCalculations. При этом сделал так:
После чего, в моих кодах вместо вызова DeInit происходил вызов PerformCalculations.
Это и понятно, так как на позицию, где ранее в таблице виртуальных функций был DeInit "переехал" PerformCalculations.
В общем — проблему я пофиксил. Тем не менее, пока открыт такой вот вопрос:
Я полагаю, что все новые вирт-методы должны добавляться после DeInit, это обеспечит запуск старых плагинов.
Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами),
мотивируя это тем, что все плагины также придётся пересобирать.
А как бы сделалы Вы, уважаемые?
P.S. Технологию COM не предлагать.
Я раньше много работал с COM и знаю эту технологию, однако использовать в нашем проекте не хочу.
COM добавляет лишние сложности там, где их совсем даже не требуется.
Здравствуйте, AlexGin, Вы писали:
AG>Я полагаю, что все новые вирт-методы должны добавляться после DeInit, это обеспечит запуск старых плагинов. AG>Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами), AG>мотивируя это тем, что все плагины также придётся пересобирать.
вы там определитесь нужна ли обратная совместимость со старыми плагинами или нет.
как только определитесь, то и решение сможете принять
насчет обратной совместимости: я работал с подобной системой и там виртуальные методы добавлялись в конец (там обязательно надо было поддерживать обратную совместимость) и вот однажды после добавления очередного метода совместимость сломалась.
оказалось, что если добавляется метод с именем, который уже есть (но с другим аргументами, к примеру), то студийный компилятор ломает порядок методов, группируя методы с одинаковым именем
так что будьте внимательны
Здравствуйте, uzhas, Вы писали:
U>вы там определитесь нужна ли обратная совместимость со старыми плагинами или нет.
Вообще-то НЕ обязательна.
Так как все плагины всё равно завязаны на головное приложение.
Посторонних разработчиков/компаний, которые бы занимались клепанием плагинов к нашей системе пока нет.
Точнее — пока что не предвидится.
U>как только определитесь, то и решение сможете принять
Ну ИМХО для решения важно также и смотреть вперёд — сегодня совместимость не актуальна, а завтра...
U>насчет обратной совместимости: я работал с подобной системой и там виртуальные методы добавлялись в конец (там обязательно надо было поддерживать обратную совместимость) и вот однажды после добавления очередного метода совместимость сломалась. U>оказалось, что если добавляется метод с именем, который уже есть (но с другим аргументами, к примеру), то студийный компилятор ломает порядок методов, группируя методы с одинаковым именем U>так что будьте внимательны
Это интересный факт.
Перегрузка виртуальных методов, в общем-то также может иметь место. Здесь спорить не буду.
Получается, что совместимость основанная на таком предположении, что добавляем вирт-методы в конец — несостоятельная
Здравствуйте, ffk, Вы писали:
ffk>Здравствуйте, AlexGin, Вы писали:
ffk>Это очень странное решение завязываться на бинарное представление vtbl
Да, решение это не самое лучшее, сам чувствую.
Посему и совета спрашиваю
ffk>Правильнее, на мой взгляд, сделать новый интерфейс:
ffk>
ffk>class IPluginlInterface2 : public IPluginlInterface
ffk>{
ffk> virtual long SuspendCalculations() = 0; // Добавлен этот новый вирт-метод!!!
ffk>}
ffk>
Возможно, что это хорошая идея
ffk>Все новый плагины будут использовать его, а в программе сделать враппер для старых интерфейсов: ffk>
Решение вроде как интересное...
В то же время: здесь и наследование (от базового IPluginlInterface) и агрегация (мембер old_plugin).
Не слишком ли много всего?
Может какой популярный паттерн проектирования на эту тему есть?
Здравствуйте, AlexGin, Вы писали:
ffk>>[/ccode] AG>Решение вроде как интересное... AG>В то же время: здесь и наследование (от базового IPluginlInterface) и агрегация (мембер old_plugin). AG>Не слишком ли много всего? AG>Может какой популярный паттерн проектирования на эту тему есть?
Да. Wrapper aka Adapter. Наследование и агрегирование тут нормально. Вам надо "старый" плагин заставить работать с новыми требованиями. Для этого вы оборачиваете каждый метод. Если у вас меняется логика вызова методов, то вы это обработаете во врапере. Например, в первой версии интерфеса гараниторовалось, что в метод SetDataItem1 не могут передать nullptr, а во второй вы этого не гарантируете, то можете обработать это во врапере. В таком подходе точка совместимости локализована. Ваша программа может вообще забыть о старых интерфейсах и контрактах для них и работать только с новыми.
P.S. Наследование не обязательно, я бы его не делал.
Здравствуйте, AlexGin, Вы писали:
AG>А как бы сделалы Вы, уважаемые?
Если всё под винду, то я бы выбрал скорее всего COM, а всю сложность работы с ним спрятал в билдовую систему и заствил/научил всех использовать АТЛ. AG>COM добавляет лишние сложности там, где их совсем даже не требуется.
Где, например?
Здравствуйте, Kernan, Вы писали:
K>Здравствуйте, AlexGin, Вы писали:
AG>>А как бы сделалы Вы, уважаемые?
Есть ещё один вариант, подключать плагины не бинарно, а через некоего брокера(например, dbus) или, например, популярные сегодня message queue. В самом просто приближении, каждый плагин общается с ядром через loopback сокет, в этом случае не надо заморачиваться с ABI, а добавление новых сообщений будет проходить очень быстро + нет проблем с обратной совместимостью. Концептуального отличия от COM правда тут не так много.
Здравствуйте, Kernan, Вы писали:
K>Здравствуйте, AlexGin, Вы писали:
AG>>А как бы сделалы Вы, уважаемые? K>Если всё под винду, то я бы выбрал скорее всего COM, а всю сложность работы с ним спрятал в билдовую систему и заствил/научил всех использовать АТЛ.
Пока под винду, но мы используем Qt 5.9 и в перспективе можем перейти на кроссплатформу (Linux: Ubuntu, Debian).
Насчёт билдовой системы: работаем с QMake, которую можно применять как в родном для неё QtCreator, так и в MSVC (через QtVsAddin).
На данной стадии проекта применять что-то другое, кроме данного простого решения, не планируем.
AG>>COM добавляет лишние сложности там, где их совсем даже не требуется. K>Где, например?
Как в своих основах:
1) Базовый интрефейс IUnknown (вот: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680509(v=vs.85).aspx )
имеет в своём составе вирт-методы AddRef и Release. Да, в 1990-х это было актуально, однако теперь,
когда уже есть официально признанный std::shared_ptr ИМХО идея IUnknown потеряла всякую актуальность.
2) Лишнее наследование, совсем не продиктованное требованиями прикладной задачи, — также оверхед.
Так и в реализации:
a) Генерация сущностей в виде GUID, призванная исключить "DLL hell", кажется мне всё таки избыточной, за исключением ситуации —
если мы компания уровня M$, предлагающая свои продукты миллиардной аудитории
b) Язык определения интерфейсов MIDL ( https://msdn.microsoft.com/en-us/library/windows/desktop/aa367091(v=vs.85).aspx ) также
лишняя (в конетксте прикладных задач) сущность. Её наличие только усложняет коды проекта.
c) Поддержка и развитие проектов на базе COM куда более трудозатратная, чем без COM.
Здесь я подразумеваю как все пользовательские действия (в той же студии), котрые направлены на поддержку инфраструктуры COM, однако,
не актуальны (от слова совсем) для прикладной задачи; так и процесс сопровождения (документация по проекту и её поддержка).
Кроме всего этого, я (как тимлид) применяя COM должен набрать в мою рабочую группу людей, владеющих данной (ныне не актуальной) технологией,
или обучить существующую команду работой с нею. Это также — лишние телодвижения
P.S. Да, первоначельная идея — сделать COM компонент и применять в разных языковых средах (C, C++, VB) —
зародившаяся примерно четверть века назад, была революционна, но теперь идея актуальность потеряла, а излишняя сложность — осталась
Здравствуйте, AlexGin, Вы писали:
AG>Получается, что совместимость основанная на таком предположении, что добавляем вирт-методы в конец — несостоятельная
есть определенные хитрости, которые надо держать в голове.
на самом деле ABI на C++ интерфейсах — вполне работоспособная тема. она хороша своей простотой и отсутствием накладных расходов на вызовы. но нужна определенная аккуратность и бдительность.
сложные типы (например, std::vector) не пробрасываются через такое API
на данный момент я также работаю с подобной системой, так что они вполне распространены
как говорится, не так страшен чёрт, как его малюют
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, AlexGin, Вы писали:
AG>>Получается, что совместимость основанная на таком предположении, что добавляем вирт-методы в конец — несостоятельная
U>есть определенные хитрости, которые надо держать в голове. U>на самом деле ABI на C++ интерфейсах — вполне работоспособная тема. она хороша своей простотой и отсутствием накладных расходов на вызовы. но нужна определенная аккуратность и бдительность.
+100500
Вы, уважаемый uzhas, под выделенным, подразумевали примерно это: https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi http://alenacpp.blogspot.com.by/2007/03/c-abi.html
U>сложные типы (например, std::vector) не пробрасываются через такое API
+100500
Да, сам объект в интерфейсе метода некошерен!
Но указатель на объект типа std::vector передавать (на уровне интерфейсного метода приведённый к типу void*),
IMHO вполне возможно...
U>на данный момент я также работаю с подобной системой, так что они вполне распространены U>как говорится, не так страшен чёрт, как его малюют
Вот и мне не хочется "тащить в проект" сложность COM, чтобы весь код проекта рос как "снежный ком"
Здравствуйте, AlexGin, Вы писали:
AG>Пока под винду, но мы используем Qt 5.9 и в перспективе можем перейти на кроссплатформу (Linux: Ubuntu, Debian).
В QT же есть библиотека для создания плагинов. Почему её не использовать? Оно работает и как раз решает все описанные выше проблемы.
P.S. Я не согласен с твоими выводами по поводу COM, но спорить тут нет смысла.
да, это оно
U>>сложные типы (например, std::vector) не пробрасываются через такое API AG>+100500 AG>Да, сам объект в интерфейсе метода некошерен! AG>Но указатель на объект типа std::vector передавать (на уровне интерфейсного метода приведённый к типу void*), AG>IMHO вполне возможно...
проброска std контейнеров между разными модулями (exe/dll) — еще более хрупкое ABI. я и с такими работал. тут обычно делают так, что все бинари собираются одновременно одним компилятором с одними и теми же настройками для C++ runtime, в частности, отключают статический рантайм ну и с debug/release надо не путаться.
я бы не рекомендовал идти таким путем. т.к. в голове еще больше деталей надо держать)
лучше не передавать контейнеры, т.к. в разных модулях они могут иметь немного разную реализацию.
вот мы из одного бинаря во второй передали пусть даже указатель на std::vector. может ли второй бинарь хотя бы проитерироваться по этому вектору? только при определенных обстоятельствах, как то настройки компиляции STL (например, https://stackoverflow.com/questions/4738987/iterator-debug-level-error-in-visual-studio )
лучше передать T* + int size
Здравствуйте, Kernan, Вы писали:
K>Здравствуйте, AlexGin, Вы писали:
AG>>Пока под винду, но мы используем Qt 5.9 и в перспективе можем перейти на кроссплатформу (Linux: Ubuntu, Debian). K>В QT же есть библиотека для создания плагинов.
+100500
Так мы именно её и используем в нашем проекте!
Данный вопрос, по развитию интерфейса, применение QtPlugin не отменяет.
В моём первоначальном посте я не указывал, что применяется QtPlugin, просто потому, что не хотел загружать коллег по форуму лишними деталями.
K>Почему её не использовать? Оно работает и как раз решает все описанные выше проблемы.
Там добавляется (на уровне интерфейса плагина), дополнительное наследование от QObject.
Как это может решить вопрос, указанный в первоначальном моём посте данного топика?
Если имеете какое-либо виденье по данному аспекту, уважаемый Kernan, пожалуйста изложите его.
K>P.S. Я не согласен с твоими выводами по поводу COM, но спорить тут нет смысла.
Я высказал мою точку зрения, сформированную во время работы с COM в 2000-х.
Эта технология применялась в стенах нескольких компаний, в котрых я тогда работал.
Здравствуйте, kov_serg, Вы писали:
_>Здравствуйте, AlexGin, Вы писали:
AG>>А как бы сделалы Вы, уважаемые? _>Я бы скриптовый язык lua для плагинов использовал.
Стоит ли вводить новые сущности в проект?
Тем более, что всё (в плане интерфейса плагинов) уже сделано, возможны некоторые небольшие корректировки.
В общем, подсистема плагинов нас вполне устраивает, небольшой вопрос, описанный мною в начале топика — должен иметь адекватное решение.
Здравствуйте, AlexGin, Вы писали:
AG>Так мы именно её и используем в нашем проекте!
Видимо, не совсем правильно. AG>В моём первоначальном посте я не указывал, что применяется QtPlugin, просто потому, что не хотел загружать коллег по форуму лишними деталями.
Самое важное и не указываешь. K>>Почему её не использовать? Оно работает и как раз решает все описанные выше проблемы. AG>Там добавляется (на уровне интерфейса плагина), дополнительное наследование от QObject.
Это не так. Там добавляется макрос в класс и после объявляения класса после чего прогоняется через метакомпилятор QT, изучи документацию получше, там всё это есть. Я бы на твоём месте сдел минимальный проект и поисследовал работу с qt-плагинами или скачал минимальный пример где они используются и поковырял его. AG>Как это может решить вопрос, указанный в первоначальном моём посте данного топика?
Изучив доки и поняв как это всё нужно правильно использовать ты, скорее всего, решишь свои проблемы.
Здравствуйте, AlexGin, Вы писали:
вопрос:
AG>Я полагаю, что все новые вирт-методы должны добавляться после DeInit, это обеспечит запуск старых плагинов. AG>Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами), AG>мотивируя это тем, что все плагины также придётся пересобирать.
AG>А как бы сделалы Вы, уважаемые?
Как вариант, сделать первый метод интерфейса, чтобы версию интерфейса возвращал. И менять его при всех несовместимых изменениях.
Тогда старые плагины можно выкидывать на этапе загрузки, а не смотреть на непредвиденное поведение после.
Здравствуйте, AlexGin, Вы писали:
AG>Здравствуйте, kov_serg, Вы писали:
_>>Здравствуйте, AlexGin, Вы писали:
AG>>>А как бы сделалы Вы, уважаемые? _>>Я бы скриптовый язык lua для плагинов использовал. AG>Стоит ли вводить новые сущности в проект?
Зависит от задач проекта. Скриптовый язык более гибкий и позволяет обычным текстовым редактором дополнить недостающий функционал.
В отличие от бинарника, можно исправлять ошибки в уже имеющихся дополнять или на их основе делать похожие. Низкий порог входа.
Легковесность. Более выразителен. При необходимости спокойно может вызывать бинарные либы для выполнения тяжелых вычислений.
AG>Тем более, что всё (в плане интерфейса плагинов) уже сделано, возможны некоторые небольшие корректировки. AG>В общем, подсистема плагинов нас вполне устраивает, небольшой вопрос, описанный мною в начале топика — должен иметь адекватное решение.
AG>Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами),
Должна быть бинарная совместимость и механизм проверки версий.
Я бы добавил еще цифровые подписи чтоб каждый раз на вирусы не проверять
AG>мотивируя это тем, что все плагины также придётся пересобирать.
А если плагины будут сторонние и плагин работавший в старой версии вдруг будет отсутствовать на новой.
Например плагин работающий с банковскими картами. Обновили по и карточки большее не принимаются. Красота.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, AlexGin, Вы писали: AG>вопрос:
AG>>Я полагаю, что все новые вирт-методы должны добавляться после DeInit, это обеспечит запуск старых плагинов. AG>>Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами), AG>>мотивируя это тем, что все плагины также придётся пересобирать.
AG>>А как бы сделалы Вы, уважаемые?
AG>Как вариант, сделать первый метод интерфейса, чтобы версию интерфейса возвращал. И менять его при всех несовместимых изменениях. AG>Тогда старые плагины можно выкидывать на этапе загрузки, а не смотреть на непредвиденное поведение после.
Надо что бы не плагин проверял, а приложение.
А плагин просто просил известный ему интерфейс (на момент написания плагина) к приложению.
А вот приложение может несколько десятко таких интерфейсов поддерживать.
Например у плагина есть init, done
В init плагин запрацивает интерфейс ПО наример дайте мне версию интерфейса 2.1 после либо получает её либо идёт лесом.
app_runtime rt[1];
int init(queryfn fn) {
if (fn.query(rt,APP_RUNTIME_VERSION_2_1) return -1;
rt->set_plugin_info(info);
rt->create_thread...
rt->add_menu_item...
rt->set_event_listner...
rt->add_resouce...
...
return 0;
}
void done() {
// release resources used
}