Суть вопроса в том, что у нас в разработке уже около года идёт приложение на 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
}
Здравствуйте, Kernan, Вы писали:
K>Здравствуйте, Kernan, Вы писали:
K>>Здравствуйте, AlexGin, Вы писали:
AG>>>А как бы сделалы Вы, уважаемые? K>Есть ещё один вариант, подключать плагины не бинарно, а через некоего брокера(например, dbus) или, например, популярные сегодня message queue. В самом просто приближении, каждый плагин общается с ядром через loopback сокет, в этом случае не надо заморачиваться с ABI, а добавление новых сообщений будет проходить очень быстро + нет проблем с обратной совместимостью. Концептуального отличия от COM правда тут не так много.
Здравствуйте, Kernan, Вы писали:
AG>>Там добавляется (на уровне интерфейса плагина), дополнительное наследование от QObject. K>Это не так. Там добавляется макрос в класс и после объявляения класса после чего прогоняется через метакомпилятор QT, изучи документацию получше, там всё это есть. Я бы на твоём месте сдел минимальный проект и поисследовал работу с qt-плагинами или скачал минимальный пример где они используются и поковырял его.
Примеры брал, с ними разбирался.
По крайней мере то, что заявлено авторами в примере, работает успешно.
Вот такие вот макросы (кроме обычного Q_OBJECT):
Q_PLUGIN_METADATA(IID ...)
Q_INTERFACES(...)
Возможно, следует копать именно в сторону этих макросов
AG>>Как это может решить вопрос, указанный в первоначальном моём посте данного топика? K>Изучив доки и поняв как это всё нужно правильно использовать ты, скорее всего, решишь свои проблемы.
Вполне может быть!
Подкину на вентилятор
AG>1) Базовый интрефейс IUnknown (вот: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680509(v=vs.85).aspx ) AG>имеет в своём составе вирт-методы AddRef и Release. Да, в 1990-х это было актуально, однако теперь, AG>когда уже есть официально признанный std::shared_ptr ИМХО идея IUnknown потеряла всякую актуальность.
После изучения std::shared_ptr, я пришел к выводу что это костыли для тех, кто ниасилил счетчики ссылок в начале 2000-ых
IUnknown — это не только ценный мех AddRef и Release, но и еще и QueryInterface. А если конкретнее — динамическая диспетчеризация интерфейсов (я правильно эту штуку "обозвал"?).
Плюс технология прозрачной агрегации компонент.
---
Не обязательно юзать IUnknown как есть и делать интерфейсы совместимыми с COM. Достаточно просто позаимствовать идею.
Я тут как-то исследовал эволюцию базового/внутреннего интерфейса для всех своих программ. Когда-то решил, что достаточно add_ref/release, а вместо QueryInterface вполне сгодится dynamic_cast.
Но к концу второго десятка лет непрерывного развития своей "игрушки" пришел к выводу, что query_interface таки придется прикручивать к внутренним интерфейсам (которые наружу никогда не будут выставлены).
---
UPD. Самое смешное, что когда программировал одну хреновину под .NET (куча классов с IDispose), тоже пришлось к ним прикручивать старый добрый COM счетчики. Только там это были не счетчики ссылок, а счетчики активных вызовов методов.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, AlexGin, Вы писали:
AG>А как бы сделалы Вы, уважаемые?
Ну во-первых, надо решить, нужна ли вам совместимость. Но если не нужна, то по крайней мере следовало бы проверять, что плагины совместимы с текущей версией программы.
Во-вторых, я не полагался бы на совместимость vtable, даже при добавлении функций в конец. Собственно, переход на другую версию компилятора может поломать совместимость vtable.
Гораздо безопаснее экспортировать из плагина сишный (а не плюсовый) интерфейс. Например, структурку, заполненную указателями на функции. Да, я понимаю, это неудобно, устарело и все такое. Зато это гораздо надежнее, чем то, что делаете вы.
AG>Какие тут могут быть аргументированные соображения за или против?
dynamic_cast будет работать a) если оба модуля скомпилированы с поддержкой rtti и б) если и plugin и твое приложение используют одну и ту же runtime библиотеку.
P.S. Здесь следует отметить, что как головное приложение, так и плагин создаётся в одном и том же филиале нашей компании,
даже если передаём на другой филиал, то с оговорками типа: какие библиотеки и компоненты (строго!) применяем.
Посему, если о общем случае совпадение здесь вроде как бы и НЕ очевидно, то в нашем проекте — оно просто ОБЯЗАНО БЫТЬ!
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Я тут как-то исследовал эволюцию базового/внутреннего интерфейса для всех своих программ. Когда-то решил, что достаточно add_ref/release, а вместо QueryInterface вполне сгодится dynamic_cast.
Блог интересный, мне понравилось! Пиши ещё!
Есть места, на которые любуешься
Абстрактный базовый класс (aka interface) — великолепно! Применение шаблонов (templates) — гениально!
И самое важное — когда доходишь до этого всего своим умом, а не просто вычитываешь в толковой кижице
КД>Но к концу второго десятка лет непрерывного развития своей "игрушки" пришел к выводу, что query_interface таки придется прикручивать к внутренним интерфейсам (которые наружу никогда не будут выставлены).
Есть принцип KISS — зачем же нам здесь его нарушать?
Внутренний интерфейс — что за зверь?
Если можно — может есть подходящий паттерн проектирования?
КД>--- КД>UPD. Самое смешное, что когда программировал одну хреновину под .NET (куча классов с IDispose), тоже пришлось к ним прикручивать старый добрый COM счетчики. Только там это были не счетчики ссылок, а счетчики активных вызовов методов.
P.S. Самое главное, ИМХО — осознавать ситуации, когда стреляем пушками по воробьям
Каждая проблема всегда имеет адекватное решение...
Можно придумывать решение "универсальной" проблемы, при этом плодить массу разных сложностей.
Тот же QueryInterface: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682521(v=vs.85).aspx
Он хороший, когда тиражируем различные компоненты (читай: COM компоненты) сотнями тысяч, а разновидностей их интерфейсов — около тысячи.
Если у меня количество компонентов можно пересчитать по пальцам (одной руки), а количество интерфейсов — хорошо, если пару штук, то
применение dynamic_cast<...>(...) вместо QueryInterface — заметно упрощает жизнь!
P.P.S. Просто задачи, решаемые как нашим приложением, так и его плагинной подсистемой, достаточно специфичны...
Здравствуйте, AlexGin, Вы писали:
CS>>Что в случае exe/dll связки как правило не факт. AG>Насчёт Run-Time библиотеки — очевидно, что используем одну и ту же — как для головного приложения, так и для Plugin-а:
Не очевидно. Смотря какую. Если runtime со static linkage то по определению exe и dll будут иметь разные runtime.
В случае же MinGW всё вообще может быть интереснее.
А если надо plugin отладить ? Т.е. dll debug runtime а exe release...
Короче: dynamic_cast надежно работает только внутри одного модуля (exe/dll). Всё остальное — гадание на кофейной гуще.
Здравствуйте, AlexGin, Вы писали: AG>Блог интересный, мне понравилось! Пиши ещё!
... Я там иногда гадости всякие пишу
А так, спасибо за отзыв КД>>Но к концу второго десятка лет непрерывного развития своей "игрушки" пришел к выводу, что query_interface таки придется прикручивать к внутренним интерфейсам (которые наружу никогда не будут выставлены). AG>Есть принцип KISS — зачем же нам здесь его нарушать?
Я двумя руками за простоту. AG>Внутренний интерфейс — что за зверь?
Программа разделена два слоя, которые взаимодействуют через абстрактные C++ интерфейсы (производные от t_smart_interface). Вот как раз их я и назвал "внутренним интерфейсом". Не очень удачно выразился — у меня проблемы с терминологией. AG>Если можно — может есть подходящий паттерн проектирования?
Что-то типа такого?
//! \ingroup db_obj
/// <summary>
/// Интерфейс для получения сервисных объектов компоненты
/// </summary>
/// Используется для расширения объектов connection, transaction.class COMP_CONF_DECLSPEC_NOVTABLE t_db_service_provider:public t_db_smart_interface
{
public:
//interface ------------------------------------------------------------
/// <summary>
/// Запрос сервисного объекта
/// </summary>
//! \param[in] rguidService
//! Идентификатор сервиса
//! \return
//! Возвращает указатель на сервисный объект. Если запрашиваемый
//! сервис не поддерживается, то возвращается NULLvirtual t_db_object_ptr query_service(REFGUID rguidService)=0; //throw
};//class t_db_service_provider
КД>>--- КД>>UPD. Самое смешное, что когда программировал одну хреновину под .NET (куча классов с IDispose), тоже пришлось к ним прикручивать старый добрый COM счетчики. Только там это были не счетчики ссылок, а счетчики активных вызовов методов. AG>P.S. Самое главное, ИМХО — осознавать ситуации, когда стреляем пушками по воробьям
И тогда хочется начать грязно ругаться. Вот как раз для этого мне и завели этот блог
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, AlexGin, Вы писали:
AG>Я полагаю, что все новые вирт-методы должны добавляться после DeInit, это обеспечит запуск старых плагинов. AG>Коллега по работе предлагает размещать так, как оно кажется логичнее (без ориентации на совместимость со старымы плагинами), AG>мотивируя это тем, что все плагины также придётся пересобирать.
Есть ещё проблема: запуск новых плагинов на старом сервере. Я тоже думал, что это несбыточно. Ещё как сбыточно.
AG>А как бы сделалы Вы, уважаемые?
Как тебе уже предлагали — запрос службы по SID и получением её рабочего интерфейса. Идея та же, что и у СОМ::QueryInterface.
Здравствуйте, Pzz, Вы писали:
Pzz>Во-вторых, я не полагался бы на совместимость vtable, даже при добавлении функций в конец. Собственно, переход на другую версию компилятора может поломать совместимость vtable.
С совместимостью все должно быть хорошо, по крайней мере для "правильных" интерфейсов:
In Visual C++ ... COM interfaces are guaranteed not to break from version to version.
Здравствуйте, ομικρον, Вы писали:
Pzz>>Во-вторых, я не полагался бы на совместимость vtable, даже при добавлении функций в конец. Собственно, переход на другую версию компилятора может поломать совместимость vtable. ομι>С совместимостью все должно быть хорошо, по крайней мере для "правильных" интерфейсов: ομι>
ομι>In Visual C++ ... COM interfaces are guaranteed not to break from version to version.
Насколько я понимаю, COM'овский интерфейс — это сишный интерфейс.
Здравствуйте, ομικρον, Вы писали:
ομι>Здравствуйте, Pzz, Вы писали:
Pzz>>Во-вторых, я не полагался бы на совместимость vtable, даже при добавлении функций в конец. Собственно, переход на другую версию компилятора может поломать совместимость vtable. ομι>С совместимостью все должно быть хорошо, по крайней мере для "правильных" интерфейсов: ομι>
ομι>In Visual C++ ... COM interfaces are guaranteed not to break from version to version.
Здравствуйте, Vi2, Вы писали:
Vi2>Есть ещё проблема: запуск новых плагинов на старом сервере. Я тоже думал, что это несбыточно. Ещё как сбыточно.
Дело в том, что в этом случае, проблема оказывается намного глубже, нежели просто интерфейс плагинов —
новые плагины на старом сервере не загрузятся (не говоря уж об исполнении).
Плохо это или хорошо — другой вопрос...
По крайней мере, проблема будет отчётливо видна сразу.
Искусственно добиваться совместимости со "старым сервером" — не вижу смысла.
Там всё равно, функционал ядра НЕ поддерживает задач, реализованных в составе новых плагинов.
AG>>А как бы сделалы Вы, уважаемые?
Vi2>Как тебе уже предлагали — запрос службы по SID и получением её рабочего интерфейса. Идея та же, что и у СОМ::QueryInterface.
+100500
Идея хорошая, реализую её в ближайшее время.
Всё равно, это значительно проще, чем тащить в проект весь COM "живьём"
Здравствуйте, AlexGin, Вы писали:
AG>Дело в том, что в этом случае, проблема оказывается намного глубже, нежели просто интерфейс плагинов — AG>новые плагины на старом сервере не загрузятся (не говоря уж об исполнении). AG>Плохо это или хорошо — другой вопрос...
А с чего бы им и не загружаться, ведь свойство загрузки одинаково для старых и новых плагинов? Вот и попросит новый плагин у старого сервера IPluginlInterface без новых, добавленных в конец, методов и вуаля. Причём обмен плагинами у пользователей находится вне поля зрения компании производителя.
Здравствуйте, Vi2, Вы писали:
Vi2>Здравствуйте, AlexGin, Вы писали:
AG>>Дело в том, что в этом случае, проблема оказывается намного глубже, нежели просто интерфейс плагинов — AG>>новые плагины на старом сервере не загрузятся (не говоря уж об исполнении). AG>>Плохо это или хорошо — другой вопрос...
Vi2>А с чего бы им и не загружаться, ведь свойство загрузки одинаково для старых и новых плагинов? Вот и попросит новый плагин у старого сервера IPluginlInterface без новых, добавленных в конец, методов и вуаля. Причём обмен плагинами у пользователей находится вне поля зрения компании производителя.
Зачем весь этот цирк
Если бы всё было именно так, — я бы взял готовый COM и не парился
Предположим такой вариант — загрузил я новый плагин на старом сервере (на старом пирложении). Что дальше?
Новый плагин, как я уже упоминал — в нашем проекте — решает несколько иные задачи, нежели старый.
Ядро приложения (точнее — старая версия ядра) НЕ ПОДДЕРЖИВАЕТ ДАННЫЙ КЛАСС ЗАДАЧ.
Просто потому, что в момент написания старой версии, ТЗ на данные задачи ещё не было сформировано.
P.S. У нас плагины реализуют некоторые вычислительные задачи, поэтому применение интерфейсов а-ля COM —
просто напросто усложнит проект, но не решит никаких (имеющихся в контексте наших задач), проблем.
Здравствуйте, AlexGin, Вы писали:
AG> AG>Зачем весь этот цирк
Я тоже не знаю, но он бывает.
AG>Предположим такой вариант — загрузил я новый плагин на старом сервере (на старом приложении). Что дальше? AG>Новый плагин, как я уже упоминал — в нашем проекте — решает несколько иные задачи, нежели старый. AG>Ядро приложения (точнее — старая версия ядра) НЕ ПОДДЕРЖИВАЕТ ДАННЫЙ КЛАСС ЗАДАЧ. AG>Просто потому, что в момент написания старой версии, ТЗ на данные задачи ещё не было сформировано.
Если интерфейсы просто отличаются наличием новых методов в конце вирт.таблицы, то получим не неподдержку данного класса задач, а падение системы. Понятно, что после такого падения эти плагины будут выброшены. Однако отношение к системы будет превратным.
А бывает, что новый плагин решают ту же задачу, но другими средствами, более быстрыми, но и не соответствующими старому серверу.
Здравствуйте, AlexGin, Вы писали:
AG>Предположим такой вариант — загрузил я новый плагин на старом сервере (на старом пирложении). Что дальше?
Плагин запрашивает версию ядра и сравнивает её с минимально поддерживаемой.
Потом начинает запрашивать по IID интерфейсы ядра, через которые он будет взаимодействовать с ним. И если вдруг обнаружит что интерфейс не поддерживается, то значит старым уже является сам плагин
AG>P.S. У нас плагины реализуют некоторые вычислительные задачи, поэтому применение интерфейсов а-ля COM - AG>просто напросто усложнит проект, но не решит никаких (имеющихся в контексте наших задач), проблем.
Вообщем тут тебе говорят, что если взялся за гушь делать систему на независимо разрабатываемых модулях — не упрощай и не пытайся изобретать велосипед
Использование dynamic_cast-ов, исключений, C++ классов через границы модулей до добра не доведет.
И если хочется простоты, не проще ли тогда скомпилировать все вместе в одном бинарнике?
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Pzz, Вы писали:
Pzz>Гораздо безопаснее экспортировать из плагина сишный (а не плюсовый) интерфейс. Например, структурку, заполненную указателями на функции. Да, я понимаю, это неудобно, устарело и все такое. Зато это гораздо надежнее, чем то, что делаете вы.
Надежно, это когда такую структуру заполняет компилятор.
А то я, #$%, знаю тут одних таких "заполнятелей".
Дозаполнялись, что получился <вырезано цензурой> Франкенштейн.
Зато типа свой.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, AlexGin, Вы писали:
AG>Для данного проекта — COM мы НЕ применяем. AG>Ввиду его большой избыточности.
И правильно. В этой подветке речь идет всего лишь о том, что есть возможность "надежного" (совместимость разных версий приложения, компиляторов, ...) использования интерефейсов с++. Чтобы увидеть это, нужно просто посмотреть на определение COM-интерфейса, вся технология COM здесь несколько сбоку.
A COM interface is nothing more than a named table of function pointers (methods), each of which has documented behavior.
By design, ... a COM interface is binary-compatible with a C++ abstract class.
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Здравствуйте, AlexGin, Вы писали:
AG>>Предположим такой вариант — загрузил я новый плагин на старом сервере (на старом пирложении). Что дальше?
КД>Плагин запрашивает версию ядра и сравнивает её с минимально поддерживаемой.
КД>Потом начинает запрашивать по IID интерфейсы ядра, через которые он будет взаимодействовать с ним. И если вдруг обнаружит что интерфейс не поддерживается, то значит старым уже является сам плагин
Плагин в данном контексте — это пассивный элемент, загружаемый и запускаемый со стороны головного модуля (приложения).
Он ничего ни у кого не запрашивает. Наоборот — головное приложение запрашивает интерфейс плагина.
AG>>P.S. У нас плагины реализуют некоторые вычислительные задачи, поэтому применение интерфейсов а-ля COM - AG>>просто напросто усложнит проект, но не решит никаких (имеющихся в контексте наших задач), проблем.
КД>Вообщем тут тебе говорят, что если взялся за гушь делать систему на независимо разрабатываемых модулях — не упрощай и не пытайся изобретать велосипед
Да, велосипеды, мопеды, мотоциклы и даже джипы не нужны, когда мы умеем делать танки!
Это ведь универсальный транспорт — и едет, и стреляет, и пасссажиров защищает.
Зачем делать другой транспорт?
КД>Использование dynamic_cast-ов, исключений, C++ классов через границы модулей до добра не доведет.
Я уже принял решение — делать так, как здесь выше советовал уважаемый товарищ c-smile: http://rsdn.org/forum/cpp.applied/6932518.1