Здравствуйте, 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++ классов через границы модулей до добра не доведет.
И если хочется простоты, не проще ли тогда скомпилировать все вместе в одном бинарнике?
-- Пользователи не приняли программу. Всех пришлось уничтожить. --