开发者

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

目录
  • 一、SO 库的基本概念
  • 二、创建 SO 库的步骤
    • 1. 准备源文件
      • math_utils.h(头文件)
      • math_utils.cpp(实现文件)
    • 2. 编译生成 SO 库
    • 三、调用 SO 库的两种方式
      • 方式 1:编译时链接(静态加载)
        • 1. 编写调用程序main.cpp
        • 2. 编译调用程序
        • 3. 运行程序
      • 方式 2:运行时动态加载(使用dlfcn.h)
        • 1. 编写动态加载程序dynamic_main.cpp
        • 2. 编译动态加载程序
        • 3. 运行程序
    • 四、高级用法:带类的 SO 库
      • 1. 定义带纯虚函数的接口类
        • calculator.h
      • 2. 实现接口类
        • calculator_impl.cpp
      • 3. 编译 SO 库
        • 4. 调用带类的 SO 库
          • use_calculator.cpp
          • 编译调用程序
      • 五、注意事项

        一、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:生成位置无关代码(javascript必选,确保库可被多个程序共享)
        • -shared:指定生成动态链接库
        • -o:指定输出文件名(惯例以lib开头,.so结尾)

        编译命令:

        g++ -fPIC -shared -o libmath_utils.so math_utils.cpp
        

        执行后会生成libmath_utils.so文件

        三、调用 SO 库的两种方式

        方式 1:编译时链接(静态加载)

        1. 编写调用程序main.cpp

        #include <IOStream>
        #includ编程客栈e "math_utils.h"
         
        int main() {
            int a = 10, b = 20;
            
            std::cout << "a + b = " << add(a, b) << std::endl;
            std::cout << "a * b = python" << multiply(a, b) << std::endl;
            
            return 0;
        }

        2. 编译调用程序

        编译时需要指定:

        • 库所在路径(-L.表示当前目录)
        • 库名称(-lmath_utils,省略lib前缀和.so后缀)

        编译命令:

        g++ m编程客栈ain.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.soandroid 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库并调用的资料请关注编程客栈(www.devze.com)其它相关文章!

        0

        上一篇:

        下一篇:

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜