• 《Cython系列》6. 使用Cython包装C、C++外部库


    楔子

    在前面的系列中我们知道了Cython如何通过提前编译的方式来对Python代码进行加速,这一节我们聚焦在另一个方向上:假设有一个C编写的库,那么如何才能让Python访问它呢?

    事实上,Python访问C编写的库,我在其它文章中介绍过。当时的方式是将C代码编译成动态库的方式,然后通过Python自带的ctypes模块来调用它,当然除了ctypes,还有swig、cffi等专门的工具。而Cython也是支持我们访问C库的,只不过它是通过包装C库的方式让我们访问。

    因为Cython同时理解C和Python,所以它可以在Python语言和C语言结合的时候控制所有的方方面面,在完成这一壮举的同时,不仅保持了Python的风格,还使得C代码更加容易定位和调试。

    如果做得好,那么Cython包装的库会具备C的性能、最小的包装开销、友好的Python接口,用户完全不需要怀疑它们正在使用的包装好的代码。

    在Cython中声明外部的C代码

    要用Cython包装C库,我们必须在Cython中声明我们使用的C组件的接口。为此,Cython提供了一个extern语句,它的目的就是告诉Cython,我们希望从指定的C头文件中使用C结构。语法如下:

    cdef extern from "header_name":
        # 相应的声明,如果不需要的话可以写上一个pass
    

    头文件使用单引号或者双引号括起来,事实上我们之前提到过。extern具有如下效果:

    • 1. Cython编译器会在生成的源文件中写入#include "header_name"
    • 2. 在extern语句块中的类型、函数以及其它声明都可以在Cython中直接使用
    • 3. Cython会在编译时检查C的声明是否正确,如果不正确会编译错误。

    extern语句块中的声明类似于C,我们会用它来介绍之前说的结构体、共同体。另外extern关键字可以和cdef组合,一起添加到任意的C声明中。

    我们说extern会在生成的源文件中写入一个#include语句,但如果我们不希望写入这个语句,但是又希望和外部代码进行交互,那么可以通过from *来禁止Cython生成。

    cdef extern from *:
        # 声明
    

    下面我们就来详细介绍extern怎么用,不过在介绍之前,我们需要了解一下extern它不会做哪些事情。

    Cython不会自动包装

    extern语句块的目的很简单,但是乍一看可能会产生误导。在Cython中存在extern块(extern声明),确保我们能够以正确的类型调用声明的C函数、变量、结构体等等。但是它不会自动地为对象创建Python的包装器,我们仍然需要使用def、或者cpdef(可能还会cdef)来调用extern块中声明的C函数。因为如果不这么做,则无法从Python代码中访问extern块中声明的外部C函数。因为Cython不会自动解析C文件、以及包装C库给外部Python访问,我们需要手动实现这一点。

    声明外部的C函数以及给类型起别名

    extern块中最常见的声明是C函数和typedef,这些声明几乎可以直接写在Cython中,只需要做一下修改:

    • 1. 将typedef变成ctypedef

    • 2. 删除类似于restrict、volatile等不必要、以及不支持的关键字

    • 3. 确保函数的返回值和对应类型的声明在同一行

      //在C中,可以这么写
      int 
      foo(){
          return 123
      }
      
    • 4. 删除行尾的分号

    此外,在Cython中声明函数时,参数可以写在多行,就像Python一样。

    下面我们定义一个C的头文件:header.h,写上一些简单的C声明和宏。

    #define M_PI 3.1415926
    #define MAX(a, b) ((a) >= (b) ? (a) : (b))
    double hypot(double, double);
    typedef int integral;
    typedef double real;
    void func(integral, integral, real);
    real *func_arrays(integral[], integral[][10], real **);
    

    然后我们看看如何在Cython中使用。

    cdef extern from "header.h":
        double M_PI
        float MAX(float a, float b)
        double hypot(double x, double y)
        ctypedef int integral
        ctypedef double real
        void func(integral a, integral b, real c)
        real *func_arrays(integral[] i, integral[][10] j, real **k)
    

    注意:我们在Cython中声明M_PI这个宏时,将其声明为double型的全局变量,同理对于MAX宏也是如此,就把它当成接收两个float、返回一个float的名为MAX函数。

    另外我们看到在extern块的声明中,我们为函数参数添加了一个名字。这是推荐的,但并不是强制的;如果有参数名的话,那么可以让我们通过关键字参数调用,对于接口的使用会更加明确。

    Cython支持所有的C声明,这些声明和C是保持高度相似的,我们只需要做简单的修改就可以直接贴在extern里面了。

    声明并包装C结构体、共同体、枚举

    Cython支持所有的C声明,这些声明和C是保持高度相似的,我们只需要做简单的修改就可以直接贴在extern里面了。

    嵌入C代码,个人写着写着突然很烦,所以不想写了,有兴趣可以自己去了解。另外除了C,Cython还可以包装C++,但笔者不会C++,所以也跳过了。

  • 相关阅读:
    谨以此文纪念一周的心血历程
    面向对象初调用:foolish 电梯
    洛谷 1016 旅行家的预算
    洛谷 1514 引水入城
    洛谷 3178 树上操作
    洛谷 3811 【模板】乘法逆元
    洛谷 1156 垃圾陷阱
    洛谷 1363 幻想迷宫
    洛谷 1736 创意吃鱼法
    洛谷 1436 棋盘分割
  • 原文地址:https://www.cnblogs.com/traditional/p/13285339.html
Copyright © 2020-2023  润新知