• Python如何调用C和C++


    ctypes库简介

    根据Python官方文档,ctypes是一个外部函数库,它提供了与C兼容的数据类型,允许调用DLL(Dynamic Link Libraries, 动态链接库)或共享库中的函数。换句话说,通过ctypes库,我们能在Python程序中调用C/C++代码。

    动态链接库是一个已编译的二进制文件,其在程序编译时并不会被链接到目标代码,而是在程序运行时才载入。Windows上的动态链接库为DLL(.dll),Linux上为SO(.so

    示例演示

    先给一个演示的Demo,然后再展开来讲,演示环境说明如下:

    Platform: Ubuntu-20.04
    gcc: 9.4.0
    
    • 1
    • 2

    首先新建一个名为test.cpp的源码文件,源码内容如下:

    extern "C"{
        void greet(char* name){
            std::cout << "Hello " << name << std::endl;
        }
        
        int sumArray(int a [], int n){
            int s = 0;
            for (int i = 0; i < n;i++){
                s += a[i];
            }
            return s;
        }
    
        double distance(double *x, double y[], int n){
            double dis;
            for (int i = 0; i < n;i++){
                dis += pow((x[i] - y[i]), 2);
            }
            return sqrt(dis);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后通过下述命令将其编译为动态链接库:

    g++ -fPIC -shared test.cpp -o test.so
    
    • 1

    Python调用上述三个函数的示例如下:

    from ctypes import *
    
    # 加载
    lib = CDLL("./test.so")
    
    lib.greet(b"Tom")
    # Hello Tom
    
    int_5 = c_int * 5
    arr = int_5(1, 3, 5, 7, 9)
    print(lib.sumArray(arr, 5))
    # 25
    
    double_2 = c_double * 2
    x = double_2(1, 3)
    y = double_2(2, 4)
    distance = lib.distance
    distance.restype = c_double
    print(distance(x, y, 2))
    # 1.4142135623730951
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    C/C++部分

    从上述演示示例可以看出,对于C/C++源码仅需要将其编译为动态链接库即可,上述的示例命令为:

    g++ -fPIC -shared test.cpp -o test.so
    
    • 1

    说明:

    • 若为C程序使用gcc,若为C++程序则使用g++
    • -fPIC表示位置独立。
    • -shared表示编译为动态库。

    另外,需要注意在上述示例代码中还使用了extern "C" {}的用法,其作用是告知编译器按C的方式编译,编译后可以直接通过函数名调用。这在编译C++程序时是必须的,因为C++支持函数重载,导致编译后函数名会发生改变,使得不能通过函数名来对C++程序中的函数进行调用。

    注意:extern修饰代表本模块可以在外部使用,若不想暴露相应的接口,则可以使用static修饰。

    Python部分

    加载动态库

    在Python中载入动态链接库的方式如下所示:

    from ctypes import *
    
    # 方式1
    lib = CDLL("./test.so")
    
    # 方式2
    lib = cdll.LoadLibrary("./test.so")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    载入完成后便可以通过.来调用C/C++程序中的内容。

    数据类型间的对应关系

    ctypes中定义的与C兼容的基本数据类型完整版详见官网Fundamental data types。本文仅列举一些常用的:

    ctypes类型C类型Python类型
    c_bool_Bool布尔型
    c_charchar单字符字节对象
    c_intint整型
    c_floatfloat浮点型
    c_doubledouble浮点型(python不区分单精度还是双精度)

    字符串

    Python中通过ctypes传递参数类型为字符串的形式如下:

    # 形式一
    lib.travel(b"I love Python!")
    
    # 形式二:使用字符指针
    m_str = c_char_p(b"I love Python!")
    lib.travel(m_str)
    
    # 形式三
    m_str = "我爱Python!"
    lib.travel(m_str.encode())
    
    # 形式四:创建String Buffer
    m_str = create_string_buffer(("我爱Python!").encode())
    lib.travel(m_str)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其中traval是C++中遍历打印字符串的一个函数,函数定义如下:

    void travel(char * str){
        for(int i = 0;str[i];i++){
            std::cout << str[i];
        }
        std::cout << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    说明:

    • 加上b可以让字符串强制转为bytes类型,但这种方式仅限于只包含ASCII字符的字符串。
    • 若字符串中不仅包含ASCII字符,可以使用encoode()方法。
    • ctypes提供了函数create_string_buffer()来创建字符串缓冲区,其属于可修改的字符串传参方式

    下面的示例便验证了create_string_buffer能创建可修改的字符串参数。

    C++中修改函数定义:

    void increment(char * str){
        for(int i = 0; str[i]; i++){
            str[i] += 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Python中调用及结果如下:

    m_str = create_string_buffer(b"abc")
    lib.increment(m_str)
    print(m_str.value)
    # b'bcd'
    
    • 1
    • 2
    • 3
    • 4

    指针/引用

    指针可以通过ctypes中的pointer(obj)函数进行创建:

    pi = c_float(3.14)
    ptr = pointer(pi)
    print(ptr.contents)
    # c_float(3.140000104904175)
    
    • 1
    • 2
    • 3
    • 4

    引用可以通过ctypes中的byref(obj)函数进行创建:

    pi = c_float(3.14)
    ptr = byref(pi)
    
    • 1
    • 2

    注意:官网指明若只想向外部函数传递一个对象指针,使用引用更快

    下面给出一个交换两个元素值的例子:

    C++部分源码为:

    void swap(int *a, int &b){
        int t = *a;
        *a = b;
        b = t;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Python部分源码为:

    x = c_int(3)
    y = c_int(4)
    lib.swap(pointer(x), byref(y))
    print(x.value, y.value)
    # 4 3
    
    • 1
    • 2
    • 3
    • 4
    • 5

    数组类型

    创建数组类型的推荐方式是类型乘以一个正数,例如:

    int_3 = c_int * 3
    
    • 1

    传参

    通过argtypes属性可以指定函数的传参类型。示例如下:

    print_str = lib.travel
    print_str.argtypes = [c_char_p]
    
    • 1
    • 2

    返回值类型

    默认情况下都假定函数返回c_int类型,但可以通过函数对象的restype属性可以指定返回值的类型。在上述示例演示中便有一个现成的例子,其指定了返回值类型是c_double

    distance.restype = c_double
    
    • 1

    参考资料

    完成本文参考了如下资料:

  • 相关阅读:
    2022年rhce最新认证—(满分通过)
    【重识云原生】第六章容器6.1.7.1节——Docker核心技术cgroups综述
    选择正确的 React 状态管理解决方案的指南
    1546_AURIX_TC275_CPU子系统_指令耗时以及程序存储接口
    设计模式之 delegate 委托模式:Swift 实现
    粒子群算法及其Python实现
    Java web 项目Tamcat在IDEA控制台输出乱码
    Qt:信号与槽机制
    SpringMVC转发和重定向
    奇迹MU架设常见问题解决
  • 原文地址:https://blog.csdn.net/qq_42103091/article/details/127567932