
怎么用 dlopen 加载 SO 并调用导出函数(Linux)
Linux 下动态加载共享库的核心是 dlopen/dlsym/dlclose 三件套,但直接裸用容易段错误或符号找不到。关键不是“能不能加载”,而是“怎么确保函数签名完全匹配”。
-
dlopen的第二个参数建议固定用RTLD_LAZY | RTLD_GLOBAL:前者延迟解析符号,避免启动时失败;后者让后续加载的库能复用已加载的符号(比如插件依赖了主程序的某个工具函数) - 必须用
extern "C"声明插件导出函数,否则 C++ 编译器会做 name mangling,dlsym查不到——哪怕函数名看起来一样 - 获取函数指针后,务必用
reinterpret_cast转成明确的函数类型,不能用void*直接调,否则调用时参数压栈错位,行为未定义
示例插件接口定义:
extern "C" {
// 插件必须提供这个工厂函数
void* create_plugin();
void destroy_plugin(void* p);
}主程序中调用:
void* handle = dlopen("./myplugin.so", RTLD_LAZY | RTLD_GLOBAL);
if (!handle) { fprintf(stderr, "%s\n", dlerror()); return; }
auto create = reinterpret_cast<void*(*)()> (dlsym(handle, "create_plugin"));
if (!create) { /* 错误处理 */ }
void* plugin = create(); // 实际插件对象指针Windows 怎么用 LoadLibrary 和 GetProcAddress(DLL)
Win32 API 行为和 Linux 差异不小,尤其在符号可见性和路径处理上。最常踩的坑是:明明 DLL 在当前目录,LoadLibrary 却返回 NULL,且 GetLastError() 是 126(模块未找到)——这往往是因为 DLL 依赖了其他未部署的运行时或第三方 DLL。
立即学习“C++免费学习笔记(深入)”;
- 不要依赖当前工作目录;显式传入绝对路径,或用
GetFullPathName转换相对路径 - DLL 必须用
__declspec(dllexport)导出函数,且同样需要extern "C"防止 mangling;否则GetProcAddress查到的是修饰后的名字(如?create_plugin@@YAPAXXZ),肉眼不可读也不可控 - 推荐在 DLL 入口点(
DllMain)里只做资源初始化/释放,**不要在其中调用LoadLibrary或GetProcAddress**,容易引发 DLL 加载死锁
导出函数声明示例:
#ifdef BUILD_PLUGIN_DLL
#define PLUGIN_API __declspec(dllexport)
#else
#define PLUGIN_API __declspec(dllimport)
#endif
<p>extern "C" {
PLUGIN_API void<em> create_plugin();
PLUGIN_API void destroy_plugin(void</em> p);
}跨平台封装要注意哪些 ABI 和生命周期问题
看似只是把 dlopen 和 LoadLibrary 包一层,实际隐藏着几个硬性约束:C++ 对象不能跨模块边界构造/析构(尤其涉及 STL 容器或虚表时),异常不能跨 DLL/SO 边界抛出,内存分配/释放必须成对(new/delete、malloc/free 不能混用)。
- 插件接口尽量用纯 C 风格:输入输出都是 POD 类型(
int、const char*、struct),避免std::string、std::vector等——不同编译器或标准库版本下它们的内存布局可能不兼容 - 对象生命周期由插件自己管理:主程序只拿到
void*,创建和销毁都通过插件提供的函数,不 new/delete - 如果插件需回调主程序功能,应在创建时传入函数指针结构体(callback struct),而不是让插件去
dlsym主程序符号——主程序不是 DLL,没有导出表可查
为什么插件一调就崩溃,但静态链接完全正常
绝大多数“动态加载后崩溃”问题,根源不在加载本身,而在于链接期和运行期的隐含假设被打破。典型表现是:函数调用后栈被破坏、参数值乱码、访问非法地址。
- 检查编译选项是否一致:插件和主程序必须使用相同的 C++ 标准(
-std=c++17)、相同的异常模型(-fexceptions/-fno-exceptions)、相同的 RTTI 开关(-frtti/-fno-rtti) - 确认插件没有静态链接 libstdc++ 或 libc++(Linux)或静态链接 CRT(Windows);否则会带入两份全局状态(如 iostream 缓冲区、locale 数据),互相干扰
- 用
nm -D myplugin.so(Linux)或dumpbin /exports myplugin.dll(Windows)确认目标符号确实存在且未被 strip;若符号名不对,99% 是忘了extern "C"
真正麻烦的从来不是“怎么加载”,而是“怎么让两个独立编译的二进制,在无头文件、无类型信息的情况下,安全地交换数据和控制权”。每一步都要有对应验证手段,不能靠“看着像就过了”。









