dlsym找不到符号首要原因是符号未显式导出:GCC默认visibility=hidden时,即使有extern "C",函数仍被链接器丢弃;必须加__attribute__((visibility("default")))并配-fvisibility=hidden编译。

Linux 下 dlopen 加载插件时找不到符号?检查 __attribute__((visibility("default")))
默认 GCC 编译的共享库符号是隐藏的(hidden),即使你写了 extern "C" 函数,dlsym 依然返回 nullptr。这不是函数名修饰问题,而是链接期就丢弃了符号表条目。
必须显式导出工厂接口函数:在头文件中用宏统一控制可见性,例如:
#define PLUGIN_API __attribute__((visibility("default")))
extern "C" {
PLUGIN_API PluginBase* create_plugin();
PLUGIN_API void destroy_plugin(PluginBase* p);
}
编译插件时加 -fvisibility=hidden(推荐全局设,避免漏导出),否则所有符号都暴露,失去可控性。
- 不加
-fvisibility=hidden→ 即使没加default属性,符号也可能意外可见,但行为不可靠 - 只加
-fvisibility=hidden不加default属性 →create_plugin在nm -D plugin.so中完全不可见 - C++ 类成员函数不能直接用
extern "C"导出,必须包在 C 风格函数里
Windows 上 LoadLibrary 成功但 GetProcAddress 失败?确认 __declspec(dllexport) 和模块定义文件
MSVC 默认不导出任何符号。仅靠 __declspec(dllexport) 声明函数还不够——如果函数在模板实例化或内联展开后生成,可能仍不会出现在导出表中。
最稳妥方式是使用 .def 文件显式列出导出符号:
LIBRARY plugin
EXPORTS
create_plugin @1
destroy_plugin @2
然后链接时传给 linker:/DEF:plugin.def。这样能绕过编译器对模板/inline 的优化干扰。
-
__declspec(dllexport)对inline函数无效,哪怕加了extern "C" - 类静态成员函数若未在 DLL 内被 ODR-used,可能根本不会生成代码,
GetProcAddress必然失败 - 确保插件工程配置为
DynamicLibrary(不是Application),否则 CRT 初始化逻辑不同,可能导致new崩溃
如何让插件工厂支持多版本共存而不冲突?用 RTLD_LOCAL + 命名空间隔离
多个插件都链接了同名第三方库(如 protobuf 3.12 vs 3.21),直接 dlopen 默认用 RTLD_GLOBAL 会污染全局符号表,导致版本错乱甚至段错误。
必须用 RTLD_LOCAL(Linux)或保证每个插件用独立 HMODULE(Windows),并禁止插件导出非接口函数:
- 插件内部调用的
std::string、std::vector等 STL 类型,绝不能作为工厂函数参数或返回值跨 DLL 边界 —— 它们依赖 ABI 兼容性,而不同编译器/标准库版本不保证 - 工厂函数只允许传原始指针、POD 结构、
const char*等 ABI 稳定类型 - 插件内部若需日志,不要调用主程序的
log()函数,改用插件自包含的轻量日志(如写文件),避免跨模块函数调用链
插件卸载后 delete 崩溃?内存分配与释放必须严格同模块
主程序用 new 分配的对象,不能交给插件里的 delete;反之亦然。Windows 下尤其危险:不同 DLL 可能绑定不同 CRT 实例,堆句柄不互通。
解决方案只有两个:
- 工厂函数返回的对象,其析构必须由插件提供的
destroy_plugin(或类似)函数执行,该函数内部调用delete - 所有跨边界的内存操作走统一接口,例如插件提供
plugin_malloc/plugin_free,主程序只调用它们
别试图用 std::shared_ptr 自动管理 —— 默认 deleter 绑定的是当前模块的 delete,卸载插件后调用会 crash。
符号可见性不是“导出就完事”,它是动态链接的契约起点;而内存归属权一旦错位,崩溃只在卸载那一刻才爆发,调试窗口里看不到堆栈。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











