
热重载失败时 dlopen 返回 NULL 怎么定位?
常见错误不是路径错,而是依赖链断裂或符号冲突。比如任务单元里用了 std::String,但新动态库链接的 GLIBCXX 版本和主程序不一致,dlopen 就会静默失败,dlerror() 才吐出真实原因。
- 用
ldd -r your_plugin.so检查未定义符号,尤其注意STB_GNU_UNIQUE类型的符号(GCC 5+ 默认启用,容易导致重载后符号不匹配) - 主程序启动时加
LD_DEBUG=libs,bindings,观察动态库加载顺序和符号绑定过程 - 不要直接传相对路径给
dlopen,统一用realpath()转成绝对路径,避免工作目录切换导致加载失败
如何安全替换正在运行的 .so 文件而不中断任务?
不能直接 mv new.so old.so —— linux 下已 dlopen 的文件即使被 mv,内核仍持其 inode 句柄,但新进程或重载逻辑会读到新内容,造成状态撕裂。
- 把新库写入临时路径(如
/tmp/task_v2_XXXXX.so),再用rename(2)原子替换目标文件(rename在同一文件系统下是原子的) - 替换前确保旧库所有任务已自然结束(不要强制 kill),或设计任务粒度为“可中断 + checkpoint”,靠版本号隔离上下文
- 主程序里维护一个
std::atomic<bool></bool>标志位,重载触发后置为 true,新任务检查该标志决定是否走新逻辑分支
c++ 类型在跨 dlopen 边界时为什么总崩?
C++ 类型(尤其是含虚表、RTTI、静态成员)不能跨动态库边界直接传递。你不能把主程序 new 出来的 TaskBase* 传进新插件里 delete,也不能让插件返回一个继承自主程序基类的对象指针。
- 所有跨库交互只通过纯 C 接口:函数指针 +
void<em></em>上下文,例如typedef int (task_init_fn)(void* ctx); - 插件导出的结构体必须是 POD(Plain Old Data),禁止含引用、非 trivial 构造/析构、虚函数
- 若需多态行为,用“函数表”模拟:主程序定义
Struct TaskVTable { void (<em>run)(void</em>); void (<em>destroy)(void</em>); };,插件返回该结构体指针而非对象指针
windows 下 LoadLibraryEx 和 Linux dlopen 行为差异要点
核心不是 API 不同,而是 Windows 默认不允许重复加载同名 DLL(即使路径不同),而 Linux 允许。这会让跨平台热重载逻辑在 Windows 上卡住。
立即学习“C++免费学习笔记(深入)”;
- Windows 必须用
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR或显式指定完整路径,且每次重载前调用FreeLibrary—— 但要注意:如果插件里开了线程或注册了 atexit 回调,FreeLibrary可能阻塞甚至死锁 - Linux 下
dlopen同名库多次调用返回相同句柄,靠引用计数管理;Windows 下必须保证HMODULE不重复,否则GetLastError()返回ERROR_INVALID_HANDLE - 统一抽象层建议封装为“模块句柄 + 加载时间戳”,每次重载生成唯一时间戳,避免逻辑误判“还是旧库”
热重载真正难的不是加载,是状态迁移。比如一个插件里缓存了数据库连接池,重载后旧连接要不要关、新连接怎么建、正在执行的查询怎么收尾——这些没法靠 dlopen 自动处理,得在业务层约定好生命周期钩子。