Вступление


Кого не посещала, пользуясь open-source библиотеками, мысль: «Спасибо этим парням за эту крутую либу! Когда-нибудь и я напишу что-нибудь стоящее, и выложу это в public, чтоб другие пользовались!»


Да всех! Или не ?..


С приходом стандарта C++17, наши хотелки решаются гораздо быстрее и изящнее, нужно лишь захотеть воплотить в жизнь задуманную идею, и уметь применять на деле вкусняшки, которыми нас балуют ребята из WG21.
Не слишком ли много **и**? Хотя: #мыжпрограммисты, для нас это норма )) ...


Предыстория


Тема плагинов довольно интересная, потому что позволяет вносить новый функционал в Программное Обеспечение (ПО) без внесения изменений в ядро программы, но при этом должен быть продуман и написан интерфейс для взаимодействия: ПО <-> Менеджер Плагинов <-> Плагины.


У меня имеется опыт (неудачный) собеседования в фирму, в которой имеется своя плагиновая система для embedded систем, из-за запутанности архитектуры которой я завалил тестовое задание. Там много макросов внутри базовых классов от которых наследуется, и много всякого такого, что делает будни программиста серыми в моменты использования подобных решений...


В один из моментов самообразования, и прочтения статей по C++17 и было принято решение закрепить почерпнутые знания при написании системы плагинов, которая бы была понятной и легкой в использовании.


Насколько плохо или хорошо у меня это получилось — судить вам, дорогие хабровчане...


Обзор


Возможности библиотеки


  • Плагины как сервисы (выполняются в отдельных трэдах)
  • Автоматическая выгрузка неиспользуемых плагинов

Плюсы


  • Простота использования
  • Асинхронное выполнение задач
  • Header-only дизайн

Минусы


  • Если вносится в систему новый плагин, то он должен быть скомпилирован той же версией компилятора что и менеджер его загружающий (также как и libstdc++) для совместимости ABI
  • Постоянные кэсты из std::any к используемым типам?
  • Проверка фьючерсов на валидность?

Создание плагина


class myplugin : public micro::iplugin {
public:
  myplugin(float ver, const std::string& nm):micro::iplugin(ver, nm) {
    // регистрация лямбды в плагине
    subscribe<2>("sum2",
      [](std::any a, std::any b)->std::any{ return std::any_cast<int>(a) + std::any_cast<int>(b); },
      "справка к функции"
    );
  }
};

Загрузка плагина


std::shared_ptr<micro::plugins> manager = micro::plugins::get();
std::shared_ptr<micro::iplugin> myplugin = manager->get_plugin("myplugin");

if (myplugin && myplugin->has<2>("sum2")) {
  std::shared_future<std::any> result = myplugin->run<2>("sum2", 25, 25);
  result.wait();

  std::cout << std::any_cast<int>(result.get()) << std::endl;
}

Более детальные примеры смотрите на странице проекта


Вместо заключения


Мне очень приятно читать познавательные статьи на Хабре по С++ (да и не только по C++),
надеюсь и вам эта статья будет интересна и хоть сколько-нибудь познавательна.


По моему скромному мнению, либу можно юзать в проде, пусть взглянут более авторитетные C++'ки и скажут своё слово, исходники хорошо документированы и их не много, — порядка 1000 строк.


Ссылка на проект


microplugins

Комментарии (15)


  1. 5oclock
    05.09.2018 20:10
    +3

    Вот самый первый минус в итоге и отвадил меня от идеи писать свою систему плагинов на c++.


    1. dmxvlx Автор
      06.09.2018 04:41

      Суть не в том чтобы набирать или не набирать плюсы или минусы, а в том: чтобы быть собой и делать так как тебе хочется ))


    1. NordicEnergy
      06.09.2018 08:15
      +1

      Поставил плюс статье и карме, чтобы не отбивать хотелку)


  1. sojuznik
    06.09.2018 04:31
    +7

    Плагины подразумевают бинарную независимость хоста от модуля расширения. Чтобы можно было писать на разных компиляторах. У вас, правильней назвать, не плагины, а модульный дизайн.

    Имею несчастье пользоваться неудачно-разработанной плагинной системой, где тоже С++. Хуже решения не придумать. Только один компилятор и трудности изменения API. Если хочется хорошую плагинную систему и оставить C++, лучше сделать прослойку: C++ на хосте оборачивается в C вызовы. А в хидере для плагина C вызовы оборачиваются снова в C++. В итоге можно пользоваться и С и C++. И не забывать важную идею: в плагине и хосте разные менеджеры памяти: где память выделили, там ее и надо освобождать.

    Насколько я понял, вас интересует обратная связь. Вот мои размышления:

    А про код сказать мало что могу, потому что не вникал. Я предпочитаю, чтобы код был как можно более проще, чтобы видя несколко десяток строк одновременно, было сразу ясно, что это и как это работает. А у вас слишком много модного C++, слишком многословно, слишком расточительно и от std:: рябит в глазах. Т.е. прямая противоположность того, что я считаю красивым и удобным кодом.

    Как я понимаю, вы это писали, видимо, для резюме, чтобы показать свои знания модного C++. Ход может быть неплохом, чтобы найти работу. Но обычно в коммерческом программировании важно не знание самого модного, а умение писать просто и элегантно по принципу KISS.

    Простота обеспечивается как техническими решениями, так и простотой с точки зрения того, кто будет пользоваться вашим кодом. Обилие всевозможных фич C++ в вашем решении говорит об обратном. Это означает, что ваше решение завязано на эти фичи, что дает меньше гибкости для пользователей вашего кода.

    Если хочется применить что-то техническое (типа специфичных фич языка или библиотек), то лучше сначала подумать, можно ли обойтись без этого, есть ли другие более элегантные методы достижения технической задачи? По-возмжности, лучше выбирать те решения, какие не ограничивают пользователя, не замедляют программу, не приводят к простыням кода, какой требуется еще написать, чтобы сделать что-то минимально-полезное.

    Еще внимательней относитесь к именам. У вас я вижу get и get_plugin, has и другие слишком общие слова. Одинаковые и похожие имена, а также слишком общие названия могут затруднять чтение кода и усложнить понимание того, для чего они нужны.

    По поводу шаблонов в API. Старайтесь применять шаблоны, только если они дают элегантное и простое решение технических задач. Использовать шаблоны для создания сахара может быть неудобным решением, поскольку параметры шаблона не очевидны для понимания пользователем кода, что именно туда передается и почему. Всякие <2>, ::_3 и прочее — это коряво и выглядит как хак. К сожалению, в стандартной библиотеке такого полно. Там это делалось для ухищрений. В своем коде ухищряться подобным образом — не лучшее решение для подражания.


    1. dmxvlx Автор
      06.09.2018 04:34
      -2

      Уважаемый sojuznik спасибо вам за развёрнутые пояснения!

      > У вас, правильней назвать, не плагины, а модульный дизайн.

      Ок, учту.

      > слишком многословно, слишком расточительно и от std:: рябит в глазах.

      Я часто пользую и boost, во избежании конфликтов и неоднозначности — всегда нэймспэйсы оставляю не раскрытыми. Как-то нормально на это смотрю.

      > Как я понимаю, вы это писали, видимо, для резюме, чтобы показать свои знания модного C++. Ход может быть неплохом, чтобы найти работу. Но обычно в коммерческом программировании важно не знание самого модного, а умение писать просто и элегантно по принципу KISS.

      Нет )) я не ищу работу ))
      Опыт у меня не очень большой, но в коммерческом программировании, как мне кажется важнее быстро выдавать рабочий код, что и позволяет нам делать новый стандарт.

      Тут порог вхождения чуть выше чем в обычных случаях (в мета программировании), но в итоге позволяет писать код лаконично и быстро.

      У меня нет каких-то конкретных сейчас примеров (читал где-то), но современный компилятор компонующий код с новым стандартом выдаёт бинарник по скорости исполнения ни намного медленнее чем без, но взамен новый стандарт предоставляет гибкость и удобство.
      Тут всегда будет За и Против…

      > Простота обеспечивается как техническими решениями, так и простотой с точки зрения того, кто будет пользоваться вашим кодом. Обилие всевозможных фич C++ в вашем решении говорит об обратном. Это означает, что ваше решение завязано на эти фичи, что дает меньше гибкости для пользователей вашего кода.

      Если фичи новые, это не значит что они сложные, сложность скрыта под капотом (стандарт в нашем случае).
      Мой код завязан на эти фичи потому что они уже здесь, грядут, идут и используются (ну или готовы чтобы их использовали).

      > Еще внимательней относитесь к именам. У вас я вижу get и get_plugin, has и другие слишком общие слова. Одинаковые и похожие имена, а также слишком общие названия могут затруднять чтение кода и усложнить понимание того, для чего они нужны.

      Ок.

      > По поводу шаблонов в API. Старайтесь применять шаблоны, только если они дают элегантное и простое решение технических задач. Использовать шаблоны для создания сахара может быть неудобным решением, поскольку параметры шаблона не очевидны для понимания пользователем кода, что именно туда передается и почему. Всякие <2>, ::_3 и прочее — это коряво и выглядит как хак. К сожалению, в стандартной библиотеке такого полно. Там это делалось для ухищрений. В своем коде ухищряться подобным образом — не лучшее решение для подражания.

      <2> — Этот как раз имело своей задачей упростить взаимодействие с интерфейсом: означает количество аргументов для задачи, вычисляется в compile time.
      ::_3 — Используется только в примере, для биндинга члена функции передаваемой в плагин.

      Для подражания может не лучшее, но так легко и удобно пользовать.

      Я имел опыт пользовать <2> в классах для полей значений, которые по сути являлись динамическими, вот там да, был гемор. А тут у нас возможные значения от 0 до 6…


      1. sojuznik
        06.09.2018 13:20
        +3

        Мои замечания про std: связаны с наглядностью примеров, поясняющих возможности вашего кода для потенциальных потребителей. Примеры пишутся для того, чтобы быстро ввести в курс дела новичка дать ему базовое понимание о том, для чего нужен ваш код и подходит ли он ему. Обилие std: и новых фич языка (в том числе очень спорных, о каких уже высказались в других комментариях) говорит о том, что вы показываете не возможности самого плагина, а свое знание новых фич C++. Поэтому я еще писал про поиск работы.

        >Мой код завязан на эти фичи потому что они уже здесь, грядут, идут и используются (ну или готовы чтобы их использовали).

        Рекомендую изменить свой взгляд в сторону понимания бизнес-задач ваших пользователей. Это вам интересно использовать новые фичи стандартной библиотеки или языка. А пользователю интересно другое: решает ли ваш код его задачи, насколько применение вашего кода упростит его разработку, как внедрение вашего кода повлияет на его цели.

        И даже если вы разрабатываете плагинную архитектуру для внутренних целей, следует учитывать вопросы бизнеса, в структуре которого вы находитесь как наемный работник. Порог входа, о каком вы говорите — это дополнительные затраты бизнеса на программистов. Чрезмерное усложнение кода самым последним, самым модным — это большая нагрузка на junior программистов, что повлияет на устойчивость бизнеса и повышение затратов на персонал, повышение затрат на кодирование, поиск ошибок и сопровождение вашего кода.

        Также стремление ко всему самому новому может обернуться неожиданными ограничениями, о которых пока не известно. Вы же пишите плагинную архитектуру. Представляете, какие сложности по ее изменению возникнут потом, когда вдруг ваши технические решения окажутся неудачными и придется или мириться с этими сложностями и ограничениями или полностью все переделывать? Уже коллеги вам сказали про проверку типов. Мало того, что это небезопасно, это еще усложняет понимание кода, раздувает его, что обязательно приведет в дальнейшим проблемам: разработчики будут тратить больше мысленных усилий на чтение и понимание кода и будут делать в итоге больше ошибок, удерживая значительную часть памяти ненужными усложнениями.

        Надеюсь, мои комментарии были полезны для вас и развития других читателей. Пишу больше для аудитории, а не только для автора статьи. Поэтому не воспринимайте их как критику именно вас или вашего опыта.


    1. AxisPod
      06.09.2018 08:52

      С чего это вдруг плагины прям вот должны иметь возможность писаться на разных языках? При такой формулировке особо ничего и не сделаешь. Плагин зачастую должен иметь общаться с сервисами основной программы, как это в вашем варианте?


      1. sojuznik
        06.09.2018 12:42
        +1

        Вопросы «зачем нужен плагин» и последующий «зачем нужна возможность писать плагин на разных языках» возникает из бизнес целей архитектуры Хост ? Плагин.

        Плагины помогает программу делать более популярной и более привлекательной для пользователей. Также это помогает независимым разработчикам реализовать возможность построить бизнес на разработке плагинов. Некоторые инди-разработчики и даже целые команды живут исключительно с этого дохода.

        Некоторые программы специально разрабатываются как хосты для совместной работы плагинов независимых разработчиков, имея только базовый, ограниченный функционал, но большую экосистему плагинов. Большинство хостов для обработки видео, аудио, изображений, текста немыслимы сейчас без возможности установки сторонних плагинов.

        Для конечных пользователей это возможность кастомизировать применение программы в своей области. Пользователем может быть и крупный и мелкий бизнес, где есть свой штат программистов.

        Чем проще написать плагин, чем популярней программа, — тем больше шанса, что плагинная архитектура будет задействована. И для этой задачи задача разработчика хоста — сделать эту архитектуру доступной, простой, ясной и привлекательной для разработки.

        Стандартный подход для обеспечения возможности писать на разных языках обеспечивается средствами операционной системы делить программы на независимые бинарные модули (dll, so, dylib). Можно вызывать как код из бинарного модуля и наоборот.

        Возможно написание плагинов, используя другие средства, например IPC, named pipes, вызов через потоки ввода-вывода консольного интерфейса. Если бинарная возможность ограничена, сторонний плагин может быть загружен и задействован исключительно средствами самой хост программы в виде конфигурационных файлов, скриптовых файлов на разных языках, в том числе специфичных для хоста.

        Рекомендую прибегнуть к поисковикам, чтобы рассмотреть эту тему шире.


        1. AxisPod
          06.09.2018 12:54

          Чем проще написать плагин, чем популярней программа

          Как бы независимость от API и простота написания в основном противоположны, потому что придётся изобретать велосипеды раз за разом.

          например IPC, named pipes, вызов через потоки ввода-вывода консольного интерфейса

          и система плагинов? оО, это вообще как. Плагин бинарник, я понимаю, но с какого перепуга он должен запускаться самостоятельно в своём выделенном процессе.

          Для примера посмотрите именно на существующий софт. Берём браузеры, в каком вы напишете плагины на C++, Delphi или Python, не думаю, что разработчики современных браузеров такие же «недалёкие» как и «я».

          Берем Microsoft Office, вот не пахнет вашими идеями. Берём FarManager, аналогичная ситуация.

          Если вы для себя выбрали какое-то определение отличное от используемого остальными, это ещё не означает, что ваше определение правильно.


          1. sojuznik
            06.09.2018 13:56

            У вас, вероятно, мало опыта в данных вопросах. Лично я писал плагины к разным программам на VC++, GCC, C и Delphi на профессиональной основе. Также использовал плагины, написанные на разных языках.

            А вопросы ABI-совместимости C++ в плагинной архитектуре — широко известная проблема.


          1. Wilk
            06.09.2018 15:32

            Примерами плагинов, так или иначе использующих различные варианты IPC, являются:

            • ycmd, предоставляющий сервис автодополнения, и интегрируемый в текстовые редакторы через плагины, общающиеся с ycmd-сервером через http;
            • языковые серверы, предоставляющие сервисы работы с проектами на различных языках программирования, ингенрируемые в редакторы через плагины, общаяющиеся с сервером через LSP;
            • NeoVim, в котором даже интерфейс работает через IPC;
            • удалённые плагины NeoVim, взаимодействующие с ядром редактора через IPC.


            Можно, конечно, поспорить, что ycmd и LSP-серверы не являются плагинами и т.п., но, на мой взгляд, они отражают идею по-настоящему универсальных плагинов, которые можно интегрировать буквально куда угодно. Подобные возможности интеграции обеспечиваются в первую очередь тем, что взаимодействие построено на основе IPC.


    1. Kokto
      06.09.2018 13:39

      Не могу согласиться с тем, что плагины должны быть бинарно независимы.
      Для этих целей больше подходит парадигма COM.
      Плагинная парадигма построения приложения даёт чуть больше гибкости, в отличии от обычных подгружаемых модулей, как к примеру — dll.


      1. sojuznik
        06.09.2018 14:24

        Бинарная независимость — это основная идея плагинной архитектуры. Разработчик пишет хост и предоставляет возможность расширения функционала без перекомпиляции хоста.

        COM не всегда подходит. Есть плагинные архитектуры, напоминающие COM, например, VST. Есть плагинные архитектуры на основе виртуальных функций (AviSynth), на основе посылки сообщений (OpenFX), как в бинарной форме, так и в текстовой. Есть разные подходы к тому, как регистрировать плагины, через доступ к структуре памяти со списком функций, через отдельные функции по типу dll, через бинарные ресурсы (Photoshop SDK).

        В некоторых случаях выбор плагинной архитектуры затрагивает вопросы законности, например, связывание GNU GPL кода с проприетарным кодом. Иногда это обходится плагинной архитектурой с вызовов плагина как отдельный процесс и общение с ним через пайпы консоли.


  1. AxisPod
    06.09.2018 08:44
    +2

    Кошмар, ужас. Зачем так делать? Зачем переносить проверку типов из compile-time в run-time? Почему бы не использовать специализированные интерфейсы? Шаблоны нормально позволяют это сделать. Делал работу с плагинами, при этом не просто так, а с IoC контейнером в добавок. Всё работает, всё строго типизировано, проверка ошибок в compile-time.


    1. dmxvlx Автор
      07.09.2018 07:43

      Не говори сколько ты зарабатываешь, покажи сколько ты заработал!

      Выложьте код в паблик и киньте ссылку.
      И мы тут все дружно поглядим, что у вас там такого "не ужасного" получилось ))