热更新本质是运行时替换函数指针,需满足纯函数、abi稳定、extern "c"导出、原子指针切换及跨dll内存隔离;类成员函数不可直接热更,必须通过工厂函数封装;新so必须dlopen成功后再dlsym获取符号并校验,避免空指针调用。

热更新本质是运行时替换函数指针,不是重载DLL那么简单
直接调用 dlopen/dlsym 加载新so后覆盖旧函数指针,只是热更新的最简形态。真正能用的前提是:所有热更函数必须是纯函数(无静态/全局状态)、不修改对象布局、且调用方只通过函数指针或虚表间接调用。C++类成员函数无法直接热更——this 指针绑定的是原编译期vtable地址,强行换vtable极易崩溃。
用 std::atomic<:function>></:function> 管理可变函数指针
比裸指针安全,避免更新中途调用到半写入状态。但要注意:原子操作只保函指针赋值的原子性,不保证函数体已加载就绪。典型错误是先 dlsym 取地址再赋值,而没检查返回值是否为 nullptr。
- 必须在新so
dlopen成功后,再用dlsym获取符号,失败则保留旧函数 - 赋值前用
std::atomic_thread_fence(std::memory_order_release)确保函数体内存可见 - 调用侧始终用
func_ptr.load()读取,不要缓存本地副本
Linux下so版本冲突和符号未定义的坑
dlopen("libgame.so", RTLD_NOW | RTLD_GLOBAL) 是常见写法,但容易踩两个坑:一是若旧so仍被其他模块引用,dlclose 不会真正卸载,新so里同名符号可能被旧so的符号覆盖;二是C++符号经过name mangling,dlsym 查找必须用 extern "C" 导出函数。
- 热更函数必须声明为
extern "C" void update_player_health(int hp);,否则dlsym找不到 - so文件名建议带版本号(如
liblogic_v2.so),避免ldconfig缓存干扰 - 用
nm -D liblogic.so确认目标符号确实在动态符号表中
Windows上 LoadLibrary + GetProcAddress 的兼容写法
Windows没有 RTLD_GLOBAL 等价物,多个DLL间符号不共享。如果热更模块依赖公共工具函数(比如日志),要么把工具函数也打进每个so,要么用主程序导出的函数指针表传入。
- 必须用
FreeLibrary显式卸载旧库,但需确保无线程正在执行其中代码(加锁或等待执行完成) -
GetProcAddress返回FARPROC,需强制转成具体函数类型,例如(void(*)(int))GetProcAddress(hLib, "update_player_health") - 建议在DLL入口点
DllMain中注册/注销回调,避免热更时资源泄漏
热更新最难的不是加载新代码,而是让业务逻辑主动放弃对旧状态的依赖——比如一个正在执行的协程栈帧里还存着旧函数的局部变量地址,这时候换函数体,结果不可预测。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











