dlopen 加载 .so 需用 extern "C" 导出函数,编译加 -fPIC -shared,路径为绝对或当前工作目录相对路径,dlsym 前需检查 handle 是否为空,卸载用 dlclose。

怎么用 dlopen 加载 .so(Linux/macOS)
dlopen 是 POSIX 标准的动态加载入口,不是 C++ 语言特性,而是 libc 提供的 C 接口。它只认路径和符号,不认 C++ 名字修饰(mangling),所以导出函数必须用 extern "C" 包裹,否则 dlsym 找不到。
常见错误现象:dlsym 返回 nullptr,但 dlerror() 报 “undefined symbol: xxx”——八成是没加 extern "C",或者函数声明/定义不一致。
使用场景:插件模块需独立编译、运行时按需加载,比如图像处理滤镜、协议解析器。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 编译插件时加
-fPIC -shared,例如:g++ -fPIC -shared -o libfilter.so filter.cpp - 主程序中用
void* handle = dlopen("./libfilter.so", RTLD_LAZY);,路径必须是绝对路径或相对当前工作目录(不是可执行文件所在目录) - 获取函数指针前,先检查
handle是否非空,再调dlsym(handle, "process_image"),别直接强转 - 卸载用
dlclose(handle),但注意:多次dlopen同一路径会增引用计数,dlclose不等于立即卸载
Windows 下怎么正确 LoadLibrary + GetProcAddress
LoadLibrary 和 GetProcAddress 是 Windows 原生 API,行为和 dlopen/dlsym 类似,但细节更琐碎:路径解析规则不同、符号导出方式更严格、DLL 依赖链容易断裂。
常见错误现象:程序启动时报 “找不到指定的模块”,实际是插件 DLL 依赖的另一个 DLL 没放对位置;或者 GetProcAddress 返回 NULL,但没调 GetLastError() 看具体原因(比如函数名大小写错、没 __declspec(dllexport))。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 插件源码中函数必须显式导出:
extern "C" __declspec(dllexport) int process_data(const char* input); - 编译命令示例(MSVC):
cl /LD /Fe:filter.dll filter.cpp;MinGW 用g++ -shared -o filter.dll filter.cpp - 调用
LoadLibrary(L"filter.dll")时,传宽字符字符串;路径支持相对路径,但搜索顺序是:当前目录 → 系统目录 → PATH 环境变量路径 → 可执行文件所在目录 - 如果插件依赖其他 DLL,别指望它们自动从插件目录加载——要么把依赖 DLL 放进 PATH 或系统目录,要么用
SetDllDirectory临时改搜索路径
C++ 类怎么跨 so/dll 边界安全传递
不能直接 new 一个类对象然后通过 dlsym 或 GetProcAddress 返回裸指针交给主程序 delete——两边可能用不同 CRT(Linux 上是不同 malloc 实现,Windows 上是不同堆)。new/delete 不匹配会导致崩溃或内存泄漏。
使用场景:插件需要暴露完整类接口,不只是简单函数,比如 class DecoderPlugin { public: virtual int decode(...) = 0; };
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 放弃直接传递类实例,改用“工厂函数 + 纯虚接口”模式:
- 插件导出一个
extern "C"工厂函数:extern "C" DecoderPlugin* create_decoder(); - 主程序拿到指针后,只通过虚函数调用,不关心内存布局
- 插件内部负责 new,同时提供
extern "C" void destroy_decoder(DecoderPlugin*),确保 delete 在同一模块发生
- 插件导出一个
- 避免在接口中传递 STL 容器(如
std::string、std::vector),它们的 ABI 在不同编译器/标准库版本间不兼容 - 如果必须传数据,用 C 风格结构体 + 显式内存管理(比如插件分配、主程序调用插件提供的释放函数)
为什么插件总在第二次加载时失败
本质是符号冲突或资源未清理。Linux 下多次 dlopen 同一路径,libc 会复用已加载的模块,但全局变量、静态构造函数不会重复执行;Windows 下 LoadLibrary 多次调用返回同一 HMODULE,但 DLL_PROCESS_ATTACH 只触发一次。
常见错误现象:第一次加载正常,第二次调 process() 函数时读到脏数据、崩溃、或日志显示初始化代码根本没跑。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 插件内避免依赖全局/静态对象的构造顺序,尤其不要在全局对象构造函数里做耗时或依赖外部状态的操作
- 若需“每次加载都重置状态”,把初始化逻辑挪到工厂函数里(如
create_decoder()内部 new 时初始化),而不是靠 DLL 加载时机 - Linux 下可用
RTLD_NODELETE防止卸载,但别滥用;Windows 下若真要强制重载,得先FreeLibrary再LoadLibrary,但注意:如果插件里有线程还在跑,FreeLibrary不会等它们结束,可能 crash - 调试时用
ldd filter.so(Linux)或Dependency Walker(Windows)确认依赖树干净,没有意外引入主程序已有的同名符号
插件机制看着简单,真正卡住人的地方往往不在加载本身,而在两边对“谁负责内存、谁控制生命周期、谁保证 ABI 兼容”的默认假设不一致。尤其是 C++ 类、异常、RTTI 这些东西,跨 so/dll 就自动失效,得手动绕过去。











