一、so 库的基本概念
- 动态链接库(so):在程序运行时被加载,多个程序可共享同一 so 库,节省内存
- 优点:减小可执行文件体积、便于模块更新(无需重新编译主程序)
- 核心:通过
extern "c"解决 c++ 名称修饰问题,确保库函数能被正确识别
二、创建 so 库的步骤
1. 准备源文件
假设有一个简单的数学运算模块,包含头文件和实现文件:
math_utils.h(头文件)
#ifndef math_utils_h
#define math_utils_h
// 用extern "c"包裹,避免c++名称修饰
#ifdef __cplusplus
extern "c" {
#endif
// 加法
int add(int a, int b);
// 乘法
int multiply(int a, int b);
#ifdef __cplusplus
}
#endif
#endif // math_utils_hmath_utils.cpp(实现文件)
#include "math_utils.h"
// 加法实现
int add(int a, int b) {
return a + b;
}
// 乘法实现
int multiply(int a, int b) {
return a * b;
}2. 编译生成 so 库
使用g++编译,关键参数:
-fpic:生成位置无关代码(必选,确保库可被多个程序共享)-shared:指定生成动态链接库-o:指定输出文件名(惯例以lib开头,.so结尾)
编译命令:
g++ -fpic -shared -o libmath_utils.so math_utils.cpp
执行后会生成libmath_utils.so文件
三、调用 so 库的两种方式
方式 1:编译时链接(静态加载)
1. 编写调用程序main.cpp
#include <iostream>
#include "math_utils.h"
int main() {
int a = 10, b = 20;
std::cout << "a + b = " << add(a, b) << std::endl;
std::cout << "a * b = " << multiply(a, b) << std::endl;
return 0;
}2. 编译调用程序
编译时需要指定:
- 库所在路径(
-l.表示当前目录) - 库名称(
-lmath_utils,省略lib前缀和.so后缀)
编译命令:
g++ main.cpp -o main -l. -lmath_utils
3. 运行程序
直接运行生成的可执行文件:
./main
可能的错误:
若提示error while loading shared libraries: libmath_utils.so: cannot open shared object file: no such file or directory,解决方法:
- 临时方案:
export ld_library_path=.:$ld_library_path - 永久方案:将 so 库复制到
/usr/lib或/usr/local/lib目录
方式 2:运行时动态加载(使用dlfcn.h)
这种方式无需在编译时链接库,可在程序运行中动态加载,适合插件化设计。
1. 编写动态加载程序dynamic_main.cpp
#include <iostream>
#include <dlfcn.h> // 动态加载头文件
int main() {
// 加载so库
void* handle = dlopen("./libmath_utils.so", rtld_lazy);
if (!handle) {
std::cerr << "加载库失败: " << dlerror() << std::endl;
return 1;
}
// 获取函数指针(注意:函数指针类型必须与实际函数匹配)
typedef int (*addfunc)(int, int);
addfunc add = (addfunc)dlsym(handle, "add");
typedef int (*multiplyfunc)(int, int);
multiplyfunc multiply = (multiplyfunc)dlsym(handle, "multiply");
// 检查函数是否获取成功
const char* error;
if ((error = dlerror()) != null) {
std::cerr << "获取函数失败: " << error << std::endl;
dlclose(handle);
return 1;
}
// 调用函数
int a = 10, b = 20;
std::cout << "a + b = " << add(a, b) << std::endl;
std::cout << "a * b = " << multiply(a, b) << std::endl;
// 关闭库
dlclose(handle);
return 0;
}2. 编译动态加载程序
需要链接动态加载库-ldl:
g++ dynamic_main.cpp -o dynamic_main -ldl
3. 运行程序
./dynamic_main
四、高级用法:带类的 so 库
c++ 的类也可以封装到 so 库中,但需要特殊处理(因为extern "c"不支持类)。
1. 定义带纯虚函数的接口类
calculator.h
#ifndef calculator_h
#define calculator_h
class calculator {
public:
// 纯虚函数(接口)
virtual int add(int a, int b) = 0;
virtual int multiply(int a, int b) = 0;
virtual ~calculator() {} // 虚析构函数
};
// 提供c风格的创建和销毁函数
extern "c" {
calculator* create_calculator();
void destroy_calculator(calculator* calc);
}
#endif // calculator_h2. 实现接口类
calculator_impl.cpp
#include "calculator.h"
class calculatorimpl : public calculator {
public:
int add(int a, int b) override {
return a + b;
}
int multiply(int a, int b) override {
return a * b;
}
};
// 实现c风格的创建和销毁函数
extern "c" {
calculator* create_calculator() {
return new calculatorimpl();
}
void destroy_calculator(calculator* calc) {
delete calc;
}
}3. 编译 so 库
g++ -fpic -shared -o libcalculator.so calculator_impl.cpp
4. 调用带类的 so 库
use_calculator.cpp
#include <iostream>
#include "calculator.h"
int main() {
// 创建对象
calculator* calc = create_calculator();
// 调用方法
int a = 10, b = 20;
std::cout << "a + b = " << calc->add(a, b) << std::endl;
std::cout << "a * b = " << calc->multiply(a, b) << std::endl;
// 销毁对象
destroy_calculator(calc);
return 0;
}编译调用程序
g++ use_calculator.cpp -o use_calc -l. -lcalculator
五、注意事项
- 名称修饰问题:c++ 会对函数名进行修饰(添加参数类型信息),必须用
extern "c"包裹导出函数,确保 c 风格的函数名 - 版本兼容性:修改 so 库后,若函数签名不变,调用程序无需重新编译
- 内存管理:在 so 库中分配的内存,应在同一库中释放,避免跨库内存管理问题
- 依赖问题:若 so 库依赖其他库,需要确保这些库在运行时可被找到
- 调试:可使用
nm -d libxxx.so查看 so 库导出的函数列表
通过以上步骤,你可以将 c++ 代码封装为 so 库,并灵活地在其他程序中调用,这在大型项目模块化开发中非常实用。
以上就是将c++程序打包成so库并调用的详细流程的详细内容,更多关于c++程序打包成so库并调用的资料请关注代码网其它相关文章!
发表评论