在 C++20 中引入的模块(module)特性彻底改变了传统的预处理方式。利用模块,我们可以把每个插件实现成独立的模块,做到编译时的隔离、运行时的可插拔,并显著提升编译速度。以下演示如何构建一个简单的插件框架,并演示插件的动态加载与调用。
1. 设计思路
- 核心接口:定义一个纯虚基类
PluginInterface,所有插件都需要实现此接口。 - 模块化:每个插件实现为独立的模块文件(
.cppm),只导出其实现。 - 动态加载:使用
dlopen/LoadLibrary加载编译好的共享库,获取插件入口函数返回PluginInterface*。
2. 代码示例
plugin_interface.h(核心模块)
#pragma once #include <string> export module plugin_interface; export class PluginInterface { public: virtual ~PluginInterface() = default; // 每个插件必须实现的业务方法 virtual std::string name() const = 0; virtual std::string execute(const std::string& input) = 0; }; // 插件入口点类型 using PluginCreateFunc = PluginInterface* (*)();hello_plugin.cppm(插件模块)
#pragma once export module hello_plugin; #include "plugin_interface.h" export class HelloPlugin : public PluginInterface { public: std::string name() const override { return "HelloPlugin"; } std::string execute(const std::string& input) override { return "Hello, " + input + "!"; } }; export extern "C" PluginInterface* create_plugin() { return new HelloPlugin(); }goodbye_plugin.cppm(另一插件)
#pragma once export module goodbye_plugin; #include "plugin_interface.h" export class GoodbyePlugin : public PluginInterface { public: std::string name() const override { return "GoodbyePlugin"; } std::string execute(const std::string& input) override { return "Goodbye, " + input + "!"; } }; export extern "C" PluginInterface* create_plugin() { return new GoodbyePlugin(); }main.cpp(加载并使用插件)
#include "plugin_interface.h" #include <filesystem> #include <iostream> #include <memory> #include <vector> #include <dlfcn.h> // Linux;Windows请使用 <windows.h> struct Plugin { void* handle; // 动态库句柄 std::unique_ptr <PluginInterface> instance; // 插件实例 }; // 读取目录下所有 .so 文件并加载 std::vector <Plugin> load_plugins(const std::string& dir) { std::vector <Plugin> plugins; for (const auto& p : std::filesystem::directory_iterator(dir)) { if (p.path().extension() == ".so") { void* handle = dlopen(p.path().c_str(), RTLD_LAZY); if (!handle) { std::cerr << "dlopen failed: " << dlerror() << '\n'; continue; } dlerror(); // 清除错误 auto create = (PluginCreateFunc)dlsym(handle, "create_plugin"); const char* dlsym_error = dlerror(); if (dlsym_error) { std::cerr << "dlsym failed: " << dlsym_error << '\n'; dlclose(handle); continue; } Plugin plugin{handle, std::unique_ptr <PluginInterface>(create())}; plugins.push_back(std::move(plugin)); } } return plugins; } int main() { auto plugins = load_plugins("./plugins"); std::string user = "C++"; for (auto& p : plugins) { std::cout << p.instance->name() << " => " << p.instance->execute(user) << '\n'; } // 插件自动释放 for (auto& p : plugins) dlclose(p.handle); return 0; }编译方式(Linux)
g++ -std=c++20 -fmodules-ts -c plugin_interface.h -o plugin_interface.o g++ -std=c++20 -fmodules-ts -c hello_plugin.cppm -o hello_plugin.o g++ -std=c++20 -fmodules-ts -c goodbye_plugin.cppm -o goodbye_plugin.o g++ -std=c++20 -shared -o hello_plugin.so hello_plugin.o g++ -std=c++20 -shared -o goodbye_plugin.so goodbye_plugin.o g++ -std=c++20 -fmodules-ts main.cpp plugin_interface.o -ldl -o app将生成的
hello_plugin.so与goodbye_plugin.so放入./plugins目录,运行./app即可看到插件被动态加载并执行。
3. 优点与扩展
- 编译加速:模块只编译一次,消除头文件的重复预处理。
- 封装性强:插件内部实现完全独立,只暴露接口。
- 可插拔:在运行时添加/删除插件不需要重编译主程序。
可以进一步完善:
- 在插件中使用
std::optional或std::any传递配置参数。 - 为插件提供生命周期管理(如
initialize/shutdown)。 - 用
std::filesystem实现插件热加载、自动重启。
通过上述示例,你可以快速搭建一个 C++20 模块化的插件框架,为大型项目提供可维护、可扩展的插件化解决方案。