Техническая статья • Plugin architecture

Плагинная архитектура: гибкие приложения, расширяемые функциональностью

Плагинная архитектура позволяет строить приложение как “ядро + набор модулей”, где основная функциональность поставляется отдельно: её можно добавлять, обновлять и отключать без перекомпиляции всего проекта.

C++ Plugins ABI Shared libraries CMake

Пример из практики: PluginCore — библиотека на C++ для создания приложения, полностью основанного на плагинах. Репозиторий: github.com/d3156/PluginCore

TL;DR

  • Плагины дают расширяемость: функциональность поставляется отдельными динамическими библиотеками.
  • Ядро берёт на себя загрузку, жизненный цикл и контракт (ABI), а модули — бизнес‑логику.
  • В PluginCore плагины общаются через “модели” (реестр зависимостей), а точки входа фиксированы: create_plugin/destroy_plugin.

Зачем нужна архитектура

Плагинная модель особенно хороша для утилит, агентских приложений, систем мониторинга/безопасности, где набор функций меняется со временем и зависит от окружения.

Как устроен PluginCore

Идея простая: хост создаёт объект ядра, а дальше “система живёт” за счёт плагинов.

#include <PluginCore/Core.hpp>

int main(int argc, char* argv[]) {
    PluginCore::Core core(argc, argv);
    return 0;
}
Порядок инициализации:
  • Ядро загружает плагины из ./Plugins или из каталога, заданного переменной окружения PLUGINS_DIR.
  • У каждого плагина вызывается registerArgs(), затем ядро парсит аргументы командной строки, затем вызывает registerModels().
  • После регистрации моделей вызывается postInit() у всех моделей, и только потом postInit() у всех плагинов.

Такой порядок полезен тем, что аргументы доступны до создания зависимостей, а “поздняя инициализация” позволяет стартовать потоки/таски уже после того, как все модели гарантированно существуют.

ABI и точки входа

В плагинной архитектуре критично зафиксировать контракт загрузки: ядро должно знать, как создать объект плагина и как корректно его уничтожить, не полагаясь на “совпадение” настроек компиляции и стандартных библиотек.
// Плагин обязан экспортировать C-ABI функции с точными именами:
extern "C" PluginCore::IPlugin* create_plugin();
extern "C" void destroy_plugin(PluginCore::IPlugin*);

// Важно: уничтожение идёт через destroy_plugin(), а не через delete в хосте.

Выделение памяти и освобождение должны происходить согласованно: такой явный destroy‑хук — простой и практичный способ снизить риск проблем на границе модуль/хост.

Модели и обмен данными

В PluginCore плагины обмениваются функциональностью через модели (PluginCore::IModel) и их реестр ModelsStorage: это похоже на DI‑контейнер/реестр сервисов, но с явным контролем порядка инициализации/удаления.

Практические правила:
  • Модель имеет пустой конструктор, а настоящая инициализация выполняется в init().
  • Порядок удаления контролируется deleteOrder(): меньшие значения удаляются раньше (поддерживаются отрицательные).
  • Есть удобный макрос регистрации/получения: RegisterModel(name, new_model, T) (вернёт существующую или зарегистрирует новую).
// Каркас плагина (минимум)
#include <PluginCore/IPlugin.hpp>
#include <PluginCore/ModelsStorage.hpp>

class MyPlugin final : public PluginCore::IPlugin {
public:
    void registerModels(PluginCore::ModelsStorage& models) override {
        // Регистрируй свои модели или получай чужие из реестра
    }
};

extern "C" PluginCore::IPlugin* create_plugin() { return new MyPlugin(); }
extern "C" void destroy_plugin(PluginCore::IPlugin* p) { delete p; }

Статус проекта

Проект активно развивается; в ближайшем будущем планируется первый релиз, после которого интерфейсы будут стабилизироваться и появится больше примеров плагинов “из коробки”.

Актуальное состояние и исходники: github.com/d3156/PluginCore

Другие статьи

Список строится из articles/index.json.