在 C++20 之前,插件化系统往往依赖于传统的共享库(DLL/so)以及复杂的运行时加载机制,导致跨平台兼容性、编译依赖和符号冲突等问题。C++20 引入的模块系统为构建插件化架构提供了新的思路。本文将从模块定义、编译管理、运行时装载以及插件注册四个层面,阐述如何使用 C++20 模块实现一个可插拔的插件框架,并给出完整代码示例。
1. 设计思路
- 模块化分层:核心库(Engine)与插件(Plugin)在编译时分离,核心不依赖插件实现,只通过抽象接口进行交互。
- 统一命名空间:所有模块使用统一的顶层命名空间
plugin_framework,避免符号冲突。 - 编译管理:使用
CMake的target_sources配合INTERFACE目标管理模块文件,保证每个模块只编译一次。 - 插件注册:采用 C++17 的
inline变量 +std::vector进行全局插件列表维护,插件实现通过extern "C"导出register_plugin函数,核心库在启动时动态扫描插件目录并调用。
2. 目录结构
plugin_framework/
├── CMakeLists.txt
├── core/
│ ├── CMakeLists.txt
│ ├── engine.hpp
│ └── engine.cpp
├── plugin_api/
│ ├── CMakeLists.txt
│ ├── module.hpp
│ └── module.cpp
└── plugins/
├── plugin_hello/
│ ├── CMakeLists.txt
│ ├── hello_module.hpp
│ └── hello_module.cpp
└── plugin_math/
├── CMakeLists.txt
├── math_module.hpp
└── math_module.cpp
3. 核心库 engine.hpp
#pragma once
#include <vector>
#include <memory>
#include <functional>
#include <string>
#include <filesystem>
#include <iostream>
namespace plugin_framework {
// 前向声明
struct Module;
// 抽象插件接口
struct Module {
virtual ~Module() = default;
virtual std::string name() const = 0;
virtual void execute() = 0;
};
using ModuleFactory = std::function<std::unique_ptr<Module>()>;
void load_plugins(const std::filesystem::path& dir);
void run_all();
} // namespace plugin_framework
4. 核心库 engine.cpp
#include "engine.hpp"
#include <dlfcn.h>
#include <filesystem>
#include <iostream>
namespace plugin_framework {
static std::vector<std::unique_ptr<Module>> g_modules;
void load_plugins(const std::filesystem::path& dir) {
for (auto const& entry : std::filesystem::directory_iterator(dir)) {
if (entry.is_regular_file() && entry.path().extension() == ".so") {
void* handle = dlopen(entry.path().c_str(), RTLD_NOW);
if (!handle) {
std::cerr << "dlopen failed: " << dlerror() << '\n';
continue;
}
using RegFn = void(*)();
dlerror(); // clear
RegFn reg = (RegFn)dlsym(handle, "register_plugin");
const char* dlsym_error = dlerror();
if (dlsym_error) {
std::cerr << "dlsym failed: " << dlsym_error << '\n';
dlclose(handle);
continue;
}
reg(); // 注册插件
}
}
}
void run_all() {
for (auto& mod : g_modules) {
std::cout << "Running plugin: " << mod->name() << '\n';
mod->execute();
}
}
// 插件注册入口
extern "C" void register_plugin(ModuleFactory factory) {
g_modules.emplace_back(factory());
}
} // namespace plugin_framework
5. 插件 API module.hpp
#pragma once
#include <memory>
#include "engine.hpp"
namespace plugin_framework {
struct Module {
virtual ~Module() = default;
virtual std::string name() const = 0;
virtual void execute() = 0;
};
using ModuleFactory = std::function<std::unique_ptr<Module>()>;
} // namespace plugin_framework
6. 示例插件 hello_module.hpp
#pragma once
#include <string>
#include "module.hpp"
class HelloModule : public plugin_framework::Module {
public:
std::string name() const override { return "HelloModule"; }
void execute() override { std::cout << "Hello from HelloModule!\n"; }
};
7. 示例插件 hello_module.cpp
#include "hello_module.hpp"
#include <memory>
extern "C" void register_plugin(plugin_framework::ModuleFactory factory) {
plugin_framework::register_plugin(factory);
}
static plugin_framework::ModuleFactory hello_factory = []{
return std::make_unique <HelloModule>();
};
// 动态链接库加载时自动调用
__attribute__((constructor))
static void init() {
register_plugin(hello_factory);
}
8. CMake 配置
根 CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(PluginFramework LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(core)
add_subdirectory(plugin_api)
add_subdirectory(plugins)
core/CMakeLists.txt
add_library(core STATIC engine.cpp)
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
plugin_api/CMakeLists.txt
add_library(plugin_api STATIC module.cpp)
target_include_directories(plugin_api PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
plugins/CMakeLists.txt
add_subdirectory(plugin_hello)
add_subdirectory(plugin_math)
plugins/plugin_hello/CMakeLists.txt
add_library(plugin_hello SHARED hello_module.cpp)
target_link_libraries(plugin_hello PRIVATE core plugin_api)
target_include_directories(plugin_hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../..)
set_target_properties(plugin_hello PROPERTIES PREFIX "")
9. 运行步骤
mkdir build && cd build
cmake ..
make -j$(nproc)
# 生成的核心库 core/libcore.so 与插件插件共享库 plugin_hello/libplugin_hello.so 位于 build 目录
# 运行示例程序(假设你编写一个 main.cpp)
main.cpp
#include "engine.hpp"
#include <filesystem>
int main() {
plugin_framework::load_plugins("./plugins"); // 加载插件目录
plugin_framework::run_all(); // 运行所有插件
return 0;
}
编译并运行:
g++ -std=c++20 -o demo main.cpp core/libcore.so
LD_LIBRARY_PATH=plugins ./demo
10. 关键点回顾
- 模块分离:核心库不直接引用插件实现,降低编译耦合。
- 统一注册机制:使用
extern "C"+__attribute__((constructor))自动在库加载时注册插件。 - 共享库命名:去掉默认前缀
lib,让插件更易于识别与加载。 - 编译系统:CMake 的
target_sources与INTERFACE目标可进一步简化模块化管理。
通过上述方式,你可以在 C++20 环境下快速搭建一个可插拔、跨平台的插件化架构。未来还可以扩展为热更新、版本管理、权限隔离等高级功能。祝你编码愉快!