zoukankan      html  css  js  c++  java
  • 《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++,所以也跳过了。

  • 相关阅读:
    Atitit 人脸识别 眼睛形态 attilax总结
    Atitit 手机号码选号 规范 流程 attilax总结 v2 r99.docx
    atitit 板块分类 上市公司 龙头企业公司 列表 attilax总结.docx
    Atititi atiitt eam pam资产管理 购物表去年.xlsx
    使用cmd查看电脑连接过的wifi密码(一)
    常见十大web攻击手段 悟寰轩
    常见web攻击方式 悟寰轩
    【MYSQL数据库】MYSQL学习笔记mysql分区基本操作 悟寰轩
    Filter及FilterChain的使用详解 悟寰轩
    启动tomcat spring初始化两次问题(eg:@PostConstruct) 悟寰轩
  • 原文地址:https://www.cnblogs.com/traditional/p/13285339.html
Copyright © 2011-2022 走看看