Windows用LoadLibrary/GetProcAddress、Linux/macOS用dlopen/dlsym加载动态库;需注意路径、导出声明、函数签名匹配、错误检查及资源释放。

Windows 下用 LoadLibrary 和 GetProcAddress 加载 DLL
Windows 不支持 dlopen,必须用 Win32 API。核心是先加载模块,再按符号名取函数指针。常见错误是传错路径、没加 .dll 后缀、或函数声明与导出不一致。
-
LoadLibrary返回HMODULE,传入路径应为绝对路径或位于PATH或当前目录的相对路径;推荐用GetFullPathName预处理 - 导出函数必须用
extern "C"+__declspec(dllexport)(DLL 编译时),否则 C++ 名字修饰会导致GetProcAddress找不到 - 获取函数指针后务必用
typedef声明匹配签名,强制类型转换容易因参数/调用约定不一致引发栈损坏
typedef int (*add_func)(int, int);
HMODULE hMod = LoadLibrary(L"myplugin.dll");
if (hMod) {
add_func add = (add_func)GetProcAddress(hMod, "add");
if (add) {
int res = add(3, 5); // 正常调用
}
FreeLibrary(hMod); // 记得释放
}Linux/macOS 下用 dlopen / dlsym 加载 SO
POSIX 标准接口,行为统一但细节更敏感:路径、符号可见性、链接方式都影响成败。最常踩的坑是未加 -fPIC 编译 SO,或忘记 -rdynamic 导致主程序符号不可见(当插件需回调主程序时)。
-
dlopen第二个参数建议固定用RTLD_LAZY | RTLD_LOCAL:延迟绑定降低启动开销,LOCAL避免符号污染全局符号表 -
dlsym返回void*,必须显式转为目标函数指针类型,不能直接赋值给普通函数变量(C++ 类型检查严格) - SO 文件名必须带
.so后缀,且运行时需确保在LD_LIBRARY_PATH中,或用./libxxx.so绝对/相对路径
#includetypedef int (*add_func)(int, int); void* handle = dlopen("./myplugin.so", RTLD_LAZY | RTLD_LOCAL); if (handle) { add_func add = reinterpret_cast (dlsym(handle, "add")); if (add) { int res = add(3, 5); } dlclose(handle); }
跨平台封装要注意的三件事
写一套代码同时跑 Windows/Linux,不能只靠宏开关,底层语义差异必须抹平。最容易被忽略的是错误诊断和生命周期管理。
- 错误信息不可靠:
GetLastError()和dlerror()都是“上次调用”结果,必须在每次LoadLibrary/dlopen后立刻检查,中间穿插其他系统调用会覆盖它 - 函数指针类型必须统一定义,例如用
using plugin_add_t = int(*)(int,int);,避免各处 typedef 不一致 - 资源释放逻辑要对称:
FreeLibrary成功后句柄立即失效;dlclose只是减引用计数,真正卸载可能延迟——若插件内有全局对象析构依赖主程序状态,可能 crash
GetProcAddress 找不到函数的五个真实原因
不是名字拼错了那么简单。调试时优先查这几点:
立即学习“C++免费学习笔记(深入)”;
- DLL 编译时没加
__declspec(dllexport),或用了 DEF 文件但导出名写错(比如写了add@8却用"add"查) - C++ 源文件里函数没加
extern "C",导致导出的是??add@@YAHHH@Z这类修饰名 - 目标函数是类成员函数或模板实例,无法直接导出——必须包装成自由函数
- 进程位数不匹配:32 位程序加载 64 位 DLL 直接失败(
LoadLibrary返回 NULL),错误码是ERROR_BAD_EXE_FORMAT - 依赖的其它 DLL 缺失,
LoadLibrary失败但你以为是函数没找到;用Dependencies.exe(Windows)或ldd(Linux)查依赖树
动态加载本身不难,难的是让符号能被稳定、可预测地找到。别省略导出声明,别绕过类型安全强制转换,更别假设路径一定存在——这些地方出问题,日志里往往只有一行空指针解引用。











