SeniorДизайнИногдаЕщё не отвечали
Спроектируйте систему плагинов для хост-приложения на C++: плагины поставляются отдельными разделяемыми библиотеками (.so/.dll), обнаруживаются и загружаются в рантайме, а не линкуются при сборке, а реестр в хосте отслеживает загруженные. Жёсткое ограничение — бинарная совместимость: плагин, собранный другим компилятором или другой версией компилятора, чем хост, всё равно должен загружаться и корректно работать, а хост должен уметь создавать и уничтожать объекты плагина через эту границу, не полагаясь на C++ name mangling и хрупкую раскладку объектов. Опишите архитектуру: как хост и плагин договариваются о стабильном контракте, как плагин загружается и его объекты создаются и уничтожаются, и что никогда не должно пересекать границу.
Система плагинов C++ использует стабильную C ABI фабрику (extern C create_plugin), абстрактный IPlugin, динамическую загрузку через dlopen/LoadLibrary и реестр в хосте. Заголовок IPlugin должен оставаться бинарно-стабильным.
- ✗Экспортировать C++ классы напрямую без C-функции-фабрики — искажённые имена различаются между компиляторами и даже версиями компиляторов; всегда используйте extern "C" на границе
- ✗Не выгружать плагины в обратном порядке — если плагин B зависит от плагина A и A выгружается первым, vtable B указывает на уничтоженный код
- ✗Передавать STL-контейнеры через границу плагина — раскладка
std::string/std::vectorможет различаться при разных рантаймах или флагах; используйте C-типы или указатели
- →Как работает согласование версий между хостом и плагином при эволюции интерфейса IPlugin?
- →Что такое COM (Component Object Model) и как он решает проблемы C++ ABI для плагинов на Windows?
Оглавление
Архитектура системы плагинов
Интерфейс плагина (plugin_api.h — стабильный заголовок)
// plugin_api.h — включается ОБОИМИ: хостом и плагинами
#pragma once
#include <cstdint>
class IPlugin {
public:
virtual ~IPlugin() = default;
virtual const char* name() const noexcept = 0;
virtual void execute(const char* input, char* output, uint32_t out_size) = 0;
};
// C ABI: единственная точка входа, экспортируемая плагином
extern "C" {
using CreatePluginFn = IPlugin* (*)();
using DestroyPluginFn = void (*)(IPlugin*);
}
Реализация плагина (отдельная .so/.dll)
// my_plugin.cpp — компилируется в my_plugin.so
#include "plugin_api.h"
#include <cstring>
class MyPlugin : public IPlugin {
public:
const char* name() const noexcept override { return "MyPlugin"; }
void execute(const char* input, char* output, uint32_t out_size) override {
std::strncpy(output, input, out_size - 1);
output[out_size - 1] = '\0';
}
};
extern "C" {
IPlugin* create_plugin() { return new MyPlugin(); }
void destroy_plugin(IPlugin* p) { delete p; }
}
Загрузчик плагинов (хостовая сторона)
// plugin_loader.cpp
#include "plugin_api.h"
#include <dlfcn.h> // POSIX
#include <memory>
#include <stdexcept>
#include <string>
class PluginHandle {
public:
explicit PluginHandle(const std::string& path) {
lib_ = ::dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!lib_)
throw std::runtime_error(::dlerror());
auto create = reinterpret_cast<CreatePluginFn>(::dlsym(lib_, "create_plugin"));
auto destroy = reinterpret_cast<DestroyPluginFn>(::dlsym(lib_, "destroy_plugin"));
if (!create || !destroy)
throw std::runtime_error("Missing factory functions");
plugin_.reset(create(), [destroy](IPlugin* p) { destroy(p); });
}
~PluginHandle() {
plugin_.reset(); // сначала уничтожаем объект...
if (lib_) ::dlclose(lib_); // ...потом выгружаем библиотеку
}
IPlugin* get() const { return plugin_.get(); }
private:
void* lib_ = nullptr;
std::unique_ptr<IPlugin, DestroyPluginFn> plugin_{nullptr, nullptr};
};
int main() {
PluginHandle handle("./my_plugin.so");
char result[256] = {};
handle.get()->execute("hello world", result, sizeof(result));
// result == "hello world"
}
Реестр плагинов
#include <unordered_map>
#include <vector>
class PluginRegistry {
public:
void load(const std::string& path) {
auto handle = std::make_unique<PluginHandle>(path);
const char* name = handle->get()->name();
plugins_[name] = std::move(handle);
}
IPlugin* find(const std::string& name) {
auto it = plugins_.find(name);
return it != plugins_.end() ? it->second->get() : nullptr;
}
private:
std::unordered_map<std::string, std::unique_ptr<PluginHandle>> plugins_;
};
Оглавление