C++插件系统无原生反射,需extern "C"导出工厂函数;跨DLL调用须严格统一ABI、编译器与运行时;析构必须由插件提供destroy函数;注册机制应避免静态初始化顺序问题,热重载需显式清理全局状态。

LoadLibrary 和 dlopen 本身不提供反射能力,所谓“插件式反射架构”在标准 C++ 中根本不存在运行时类型发现机制——你不能靠 std::reflexpr 或任何 C++26 特性从 DLL/.so 里自动枚举类、字段或构造函数。所有可用的“反射效果”,都必须靠显式导出的 C 风格工厂函数 + 约定接口 + 手动注册来模拟。
为什么 dlsym / GetProcAddress 找不到 create_plugin 符号
这不是路径或权限问题,90% 是符号名被 C++ name mangling 破坏了。
-
extern "C"必须加在插件工程的工厂函数声明和定义两侧,漏掉任意一处都会导致导出的是?create_plugin@@YAPAVIPlugin@@XZ这类无法匹配的修饰名 - Linux 下用
objdump -T libplugin.so | grep create,Windows 下用DumpBin /exports plugin.dll直接确认导出表里是否存在未修饰的create_plugin - Qt 插件若用
QPluginLoader,必须确保插件和主程序链接同一套 Qt 库(qobject_cast失败往往是因为 Qt 构建配置不一致)
纯虚接口跨 DLL 调用崩溃的根本原因
不是代码写错了,是 ABI 不兼容。vtable 布局、sizeof、RTTI 地址、甚至成员变量对齐方式,在不同编译器/STL 版本下都可能不同。
- 主程序和插件必须用完全相同的编译器(如 MSVC 19.4x)、相同 STL(如 v143)、相同运行时选项(
/MDvs/MDd)构建 - 接口类中禁止虚继承、非平凡析构函数、
std::string成员、异常抛出、dynamic_cast或typeid - 插件导出的
IPlugin*指针,其析构必须由插件提供的destroy_plugin(IPlugin*)完成——主程序用delete会调用错的析构函数
如何设计真正可扩展的插件注册机制
别依赖全局 map 或静态初始化顺序,用函数内静态变量 + 工厂 lambda 封装注册逻辑。
立即学习“C++免费学习笔记(深入)”;
- 定义统一入口:每个插件导出
const char* plugin_type_name()和void* (*create)(),主程序按字符串查表,不依赖类型名硬编码 - 注册表用
std::map<std::string, std::function<void*()>>,但包裹在函数内:static auto& registry() { static std::map<...> r; return r; } - 每个插件源文件末尾放一行宏:
REGISTER_PLUGIN("ai_processor", []{ return new AIProcessor(); }),宏展开为静态局部 struct 构造函数注册 - 多插件同名注册会静默覆盖——注册前加
assert(registry().count(name) == 0),避免后期排查困难
插件热重载后状态残留的典型表现与修复
卸载再加载新插件,却报 “already initialized” 或日志重复打印,基本可以断定是静态变量没清理干净。
- 插件内部所有全局/静态对象(含 STL 单例、第三方 SDK 初始化标记、
std::once_flag)必须在shutdown()函数中显式重置 - 不要在插件 DLL 的 DllMain /
__attribute__((constructor))里做初始化——它们在LoadLibrary时触发,但卸载时不会对应执行__attribute__((destructor)) - 主程序调用
FreeLibrary/dlclose后,立即清空所有缓存的函数指针(如create_func = nullptr),否则下次dlsym可能返回旧地址,引发静默崩溃
复杂点不在怎么写工厂函数,而在谁负责内存、谁控制生命周期、谁保证 ABI 对齐——这些契约一旦写死,就很难改。很多团队踩坑不是因为不会写 GetProcAddress,而是默认“new 出来的东西 delete 掉就行”,结果跨 DLL 释放时直接崩在虚析构函数里。


















