将C++程序打包成SO库并调用的详细流程

 更新时间:2025年11月04日 10:03:21   作者:智驱千行  
在 Linux 系统中,SO(Shared Object)是动态链接库,类似于 Windows 的 DLL,将 C++ 程序打包为 SO 库可以实现代码复用、隐藏实现细节,并支持动态加载,以下是详细的打包与调用流程,需要的朋友可以参考下

一、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_H

math_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_H

2. 实现接口类

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

五、注意事项

  1. 名称修饰问题:C++ 会对函数名进行修饰(添加参数类型信息),必须用extern "C"包裹导出函数,确保 C 风格的函数名
  2. 版本兼容性:修改 SO 库后,若函数签名不变,调用程序无需重新编译
  3. 内存管理:在 SO 库中分配的内存,应在同一库中释放,避免跨库内存管理问题
  4. 依赖问题:若 SO 库依赖其他库,需要确保这些库在运行时可被找到
  5. 调试:可使用nm -D libxxx.so查看 SO 库导出的函数列表

通过以上步骤,你可以将 C++ 代码封装为 SO 库,并灵活地在其他程序中调用,这在大型项目模块化开发中非常实用。

以上就是将C++程序打包成SO库并调用的详细流程的详细内容,更多关于C++程序打包成SO库并调用的资料请关注脚本之家其它相关文章!

相关文章

  • C++在同一对象中存储左值或右值的方法

    C++在同一对象中存储左值或右值的方法

    C++ 代码似乎经常出现一个问题:如果该值可以来自左值或右值,则对象如何跟踪该值?即如果保留该值作为引用,那么就无法绑定到临时对象,本文给大家介绍了C++在同一对象中存储左值或右值的几种方法,需要的朋友可以参考下
    2025-03-03
  • QT实现读写ini文件的示例代码

    QT实现读写ini文件的示例代码

    .ini文件是Initialization File的缩写,即初始化文件,本文主要给大家介绍了关于Qt读写ini文件的相关方法,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • C++的命名空间详解

    C++的命名空间详解

    这篇文章主要介绍了C++编程中的命名空间基本知识讲解,包括对C++中内联命名空间新特性的介绍,需要的朋友可以参考下
    2021-09-09
  • c++获取sqlite3数据库表中所有字段的方法小结

    c++获取sqlite3数据库表中所有字段的方法小结

    本文给大家分享c++获取sqlite3数据库表中所有字段的三种常用方法,本文针对每一种方法给大家详细介绍,需要的的朋友通过本文一起学习吧
    2016-11-11
  • C++ 容器中map和unordered map区别详解

    C++ 容器中map和unordered map区别详解

    这篇文章主要为大家介绍了C++ 容器中map和unordered map区别示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • C语言中static与sizeof查缺补漏篇

    C语言中static与sizeof查缺补漏篇

    static在修饰变量的时候,如果是修饰全局变量,则跟全局变量功能一样;如果是修改局部变量,则每次调用的时候,保持着上一次的值;而sizeof是用来判断一个变量及数据类型所占字节数的,下面我们详细来看看
    2022-07-07
  • C++详解Primer文本查询程序的实现

    C++详解Primer文本查询程序的实现

    这个程序还是比较复杂的,把这句话作为文章的开头可以看出它的真实性.....这篇文章主要介绍了文本查询程序的实现,下面我们一起来看看
    2022-06-06
  • C++基于特征向量的KNN分类算法

    C++基于特征向量的KNN分类算法

    这篇文章主要为大家详细介绍了C++基于特征向量的KNN分类算法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • 使用C语言操作文件的基本函数整理

    使用C语言操作文件的基本函数整理

    这篇文章主要介绍了使用C语言操作文件的基本函数整理,包括创建和打开以及关闭文件的操作方法,需要的朋友可以参考下
    2015-08-08
  • 浅析c++ 宏 #val 在unicode下的使用

    浅析c++ 宏 #val 在unicode下的使用

    以下是对c++中宏#val在unicode下的使用方法进行了详细的分析介绍,需要的朋友可以参考下
    2013-07-07

最新评论