编译插件时需用-fvisibility=hidden并显式标记PLUGIN_API导出工厂函数,用extern "C"避免名称修饰,主程序以RTLD_LOCAL方式dlopen并按destroy→dlclose顺序卸载,插件数据传递须用POD结构体隔离ABI风险。

在 Linux/macOS 下用 C++ 编写支持运行时动态加载(dlopen/dlsym)的插件库时,必须确保工厂函数能被主程序正确查找到,同时防止插件内部符号意外泄露到全局符号表引发冲突或崩溃。
编译插件共享库时启用隐藏默认符号可见性
使用 -fvisibility=hidden 编译选项,让所有函数、类、变量默认不可被外部 dlsym 查找,仅显式标记为 __attribute__((visibility("default"))) 的符号才导出。
在插件源码(如 plugin.cpp)顶部添加宏定义:#define PLUGIN_API __attribute__((visibility("default"))),后续只对需要导出的工厂函数加此修饰。
这一步不做会导致插件中所有非 static 函数都进入动态符号表,极易与主程序或其他插件同名函数冲突,引发 undefined behavior。
立即学习“C++免费学习笔记(深入)”;
定义并导出标准化的工厂接口函数
方法一:C 风格纯函数导出(推荐)
在插件中声明一个返回基类指针的函数,用 PLUGIN_API 修饰,并确保函数签名固定:
extern "C" PLUGIN_API BasePlugin* create_plugin_instance();
注意:必须用 extern "C" 禁用 C++ 名称修饰(name mangling),否则主程序 dlsym 找不到符号——【符号名必须与 dlsym 字符串完全一致】。
方法二:导出构造器和析构器双函数
额外提供 PLUGIN_API void destroy_plugin_instance(BasePlugin* p);,避免主程序用错 delete 方式(尤其是跨模块 new / delete 不匹配)。
主程序中安全加载并调用插件工厂
第一步:调用 void* handle = dlopen("libmyplugin.so", RTLD_LAZY | RTLD_LOCAL);
【RTLD_LOCAL 是关键】:它阻止插件符号污染全局符号空间,避免后续加载其他插件时因符号重复定义而失败。
第二步:检查 handle 是否为空,若为空立即用 dlerror() 获取错误信息并退出。
第三步:用 auto create_fn = reinterpret_cast<baseplugin>(dlsym(handle, "create_plugin_instance"));</baseplugin> 获取函数指针。
第四步:检查 create_fn 是否为 nullptr,不为空则调用 BasePlugin* inst = create_fn(); 得到实例。
第五步:使用完毕后,按顺序执行 destroy_plugin_instance(inst); → dlclose(handle);。不能颠倒,否则 inst 可能访问已卸载代码段。
插件内部类型安全隔离处理
插件头文件中禁止暴露任何 STL 容器、std::string 或虚函数表布局细节给主程序头文件。
改用 POD 结构体 + 纯 C 接口封装数据传递,例如:
struct PluginConfig { int version; const char* config_path; };
工厂函数接收该结构体指针而非 std::map<:string std::string></:string>,彻底规避 ABI 兼容性风险。
这一步漏做会导致不同编译器版本或 STL 实现下插件加载后立即段错误。


















