
插件接口必须用 C 风格导出函数,不能直接导出 C++ 类
Windows 的 LoadLibrary 和 Linux 的 dlopen 只能获取 C 风格符号(无名修饰),C++ 类方法、模板、重载函数无法被跨模块直接调用。必须定义一组纯 C 函数作为“桥接入口”,比如:
-
create_plugin():返回void*或统一基类指针(需约定 ABI) -
destroy_plugin(void*):释放资源 -
get_plugin_version():返回int或const char*
所有插件 DLL/SO 必须用 extern "C" 包裹这些函数,禁用 C++ name mangling。
Linux 下 dlopen 时要传 RTLD_GLOBAL,否则符号冲突会静默失败
如果插件内部又依赖了同名但不同版本的第三方库(如 libjson.so),且主程序也链接了它,dlopen 默认使用 RTLD_LOCAL,会导致插件内符号解析失败——但不报错,只在调用时 crash。正确做法是:
void* handle = dlopen("./plugin.so", RTLD_NOW | RTLD_GLOBAL);RTLD_GLOBAL 把插件的符号注入全局符号表,让后续 dlsym 或其他插件可复用。Windows 的 LoadLibrary 默认行为类似,无需额外配置。
立即学习“C++免费学习笔记(深入)”;
插件与主程序必须共享同一份 STL / 运行时内存布局
这是最隐蔽的坑:如果主程序用 libc++.so,插件却链接了 libstdc++.so,哪怕只是传递 std::string 参数,也会因 vtable 偏移或分配器不一致导致段错误。解决方案只有两个:
- 所有模块统一编译器 + 标准库(例如全用 Clang + libc++,且都设
-stdlib=libc++) - 彻底避免跨边界传递 C++ 对象——只传
const char*、int、void*,字符串由插件自己malloc,主程序用free(前提是共用同一堆)
推荐后者,更可控。例如插件提供 const char* plugin_get_name(),主程序不 delete,也不 copy,直接用。
Windows 上 DLL 路径解析依赖 PATH 和当前目录,别硬编码绝对路径
用 LoadLibrary("myplugin.dll") 时,系统按顺序查找:exe 所在目录 → 当前工作目录 → PATH 环境变量路径。若插件放在 ./plugins/ 下,应拼接完整路径:
std::string path = "./plugins/myplugin.dll"; HMODULE h = LoadLibraryA(path.c_str());
注意:LoadLibraryW 要求宽字符路径,且当前目录可能被其他线程修改,所以不要依赖 GetCurrentDirectory 动态拼接;直接写相对路径最稳。Linux 同理,dlopen("./plugins/plugin.so", ...) 比 "plugin.so" 更可靠。
插件化真正的难点不在加载,而在 ABI 稳定性——只要接口函数签名改一个参数,所有旧插件就失效。建议早期就用小版本号控制,比如 struct PluginInterfaceV1,并预留 reserved[16] 字段。











