使用 dlopen 动态加载 .so 文件
dlopen 是 Linux/Unix 系统提供的动态加载共享库的 API,它允许程序在运行时(而非编译时)加载和使用共享库中的函数。这种方式比 CMake 直接链接更灵活,适用于插件系统或需要动态加载功能的场景。
基本使用方法
1. 包含必要头文件
cpp#include <dlfcn.h> // dlopen 相关函数#include <iostream>
2. 加载共享库
cpp
void* handle = dlopen("/path/to/library.so", RTLD_LAZY);if (!handle) { std::cerr << "Cannot open library: " << dlerror() << std::endl; return 1;}```
### 3. 获取函数指针
```cpp
// 定义函数指针类型typedef void (*my_func_t)(int);
// 获取函数my_func_t my_func = (my_func_t)dlsym(handle, "function_name");
if (!my_func) {
std::cerr << "Cannot load symbol: " << dlerror() << std::endl;
dlclose(handle);
return 1;
}
4. 使用函数
cppmy_func(42); // 调用动态加载的函数
5. 关闭库
cpp
dlclose(handle);
完整示例
假设有一个共享库 libmath.so,其中包含 add 和 subtract 函数:
共享库代码 (math.cpp)
cpp
extern "C" { int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; }}
编译为共享库:
bash
g++ -shared -fPIC -o libmath.so math.cpp
主程序代码
cpp
#include <dlfcn.h>
#include <iostream>
#include <string>
int main() {
// 1. 打开共享库
void* handle = dlopen("./libmath.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Cannot open library: " << dlerror() << std::endl;
return 1;
}
// 2. 加载函数
typedef int (*math_func_t)(int, int);
math_func_t add = (math_func_t)dlsym(handle, "add");
if (!add) { std::cerr << "Cannot load symbol 'add': " << dlerror() << std::endl; dlclose(handle); return 1; } math_func_t subtract = (math_func_t)dlsym(handle, "subtract"); if (!subtract) { std::cerr << "Cannot load symbol 'subtract': " << dlerror() << std::endl; dlclose(handle); return 1; } // 3. 使用函数 std::cout << "5 + 3 = " << add(5, 3) << std::endl; std::cout << "5 - 3 = " << subtract(5, 3) << std::endl; // 4. 关闭库 dlclose(handle); return 0;}
编译主程序
bash
g++ main.cpp -o main -ldl
重要注意事项
- 符号可见性 : - 共享库中的函数必须导出才能被
dlsym找到 - 使用extern "C"避免 C++ 的名称修饰(name mangling) -
- 错误处理 : - 每次
dlopen或dlsym后都应检查错误 -dlerror()返回最近一次错误的字符串描述
- 错误处理 : - 每次
-
- 标志参数 : -
RTLD_LAZY:延迟绑定,只在需要时解析符号(性能更好) -RTLD_NOW:立即解析所有符号(启动时检查所有符号是否存在) -RTLD_GLOBAL:使库的符号可用于后续加载的库 -RTLD_LOCAL:符号仅对本库可见(默认)
- 标志参数 : -
-
- 库搜索路径 : - 可以使用绝对路径或相对路径 - 也可以只指定库名(如
libmath.so),系统会在LD_LIBRARY_PATH中查找5. 内存管理 : - 确保在不再需要库时调用dlclose- 多次dlopen同一个库会增加引用计数
- 库搜索路径 : - 可以使用绝对路径或相对路径 - 也可以只指定库名(如
在 CMake 项目中集成
cmake
cmake_minimum_required(VERSION 3.10)
project(DynamicLoadExample)
add_executable(main main.cpp)
# 链接 dl 库target_link_libraries(main dl)
# 确保程序能找到共享库
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "$ORIGIN")
高级用法
- 加载类: - 可以使用工厂函数创建类实例 - 在共享库中定义创建和销毁对象的函数
-
- 版本控制: - 可以在库中实现版本检查函数
-
- 插件系统 : - 扫描目录加载多个插件 - 定义标准接口供插件实现
dlopen提供了极大的灵活性,但也需要开发者自己管理更多的细节,适合需要动态加载功能的场景。
- 插件系统 : - 扫描目录加载多个插件 - 定义标准接口供插件实现