windows与linux动态库加载行为差异显著:dlopen失败需立即调dlerror()保存错误,loadlibrary需getlasterror()+formatmessage;路径处理、符号查找、句柄管理、依赖解析等均需平台特判,不可简单复用逻辑。

Windows 和 Linux 下 dlopen / LoadLibrary 的行为差异必须手动对齐
跨平台动态库加载器最常崩在“打开失败却报不出原因”——dlopen 返回 nullptr 但 dlerror() 是空,LoadLibrary 返回 NULL 却没调 GetLastError()。这不是代码写错了,是两边错误机制根本不同。
-
dlopen失败时,dlerror()返回的是最后一次错误的**静态字符串指针**,调一次就清空,必须立刻保存;而LoadLibrary要配合FormatMessage(..., GetLastError(), ...)才能拿到可读信息 - 路径分隔符不是唯一问题:Linux 下
dlopen("libfoo.so")会自动加前缀和后缀,Windows 下LoadLibrary("foo.dll")**不会**自动补lib或.dll,必须传完整文件名 - Linux 默认不搜索当前目录(
RTLD_LOCAL+LD_LIBRARY_PATH不含.),Windows 默认搜当前目录、系统路径、PATH —— 这会导致同名库在两边加载到完全不同的版本
std::shared_ptr 管理句柄时,析构逻辑必须按平台分别实现
不能写一个泛型删除器直接套用,因为 dlclose 和 FreeLibrary 对“已关闭句柄”行为不一致:dlclose 对重复关闭返回 0(成功),FreeLibrary 对无效模块句柄直接崩溃。
- Linux 删除器里要检查
handle != nullptr,然后无条件调dlclose - Windows 删除器里必须先用
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, ...)验证句柄是否仍有效,再调FreeLibrary;否则热重载时容易 double-free - 别用
std::unique_ptr—— 它默认用delete,你得显式传删除器,反而更容易漏写平台分支
符号查找:C++ 函数名修饰(name mangling)让 dlsym / GetProcAddress 直接失效
除非函数用 extern "C" 声明,否则 dlsym("init") 在 Linux 和 GetProcAddress(h, "init") 在 Windows 都找不到 C++ 成员函数或模板实例 —— 因为编译器把名字改得面目全非了。
- 导出函数必须统一加
extern "C",且声明放在头文件里供主程序和插件共用 - Linux 下可用
nm -D libfoo.so | grep init查看实际符号名,Windows 下用dumpbin /exports foo.dll,别靠猜 - 如果非要导出类,用工厂函数(
extern "C" Plugin* create_plugin();),而不是直接dlsym类构造函数
插件路径和依赖库搜索顺序不一致,导致“本地测试通过,上线就报 missing symbol”
Linux 下 dlopen(RTLD_LAZY) 只在首次调用符号时解析依赖,Windows 下 LoadLibrary 会立即递归加载所有依赖 DLL —— 所以 Windows 更早暴露路径问题,Linux 却可能跑一半才崩。
立即学习“C++免费学习笔记(深入)”;
- Linux 插件依赖的
.so必须出现在LD_LIBRARY_PATH或/etc/ld.so.conf.d/里,不能只放插件目录下;Windows 插件依赖的 DLL 放同一目录即可 - 不要依赖
DT_RPATH或DT_RUNPATH:它们在交叉编译或容器环境里经常被 strip 掉;显式用setenv("LD_LIBRARY_PATH", plugin_dir.c_str(), 1)更可控(Linux) - Windows 上用
SetDllDirectory(plugin_dir.c_str())比改全局 PATH 更安全,避免污染其他模块
真正难的不是写通两个平台的加载逻辑,而是让插件开发者不用关心这些。路径、符号、错误处理、生命周期——每个点都得在抽象层做硬隔离,稍有松动,模块一多,问题就藏在看似无关的加载顺序里。










