使用模式

  • 综述

    • ABI 不使用 compiler
    • API 使用 compiler
    • in-line, 只使用 cffi 代码
    • out-of-line 还使用 .c 文件
    • 不严谨地说

      • in-line –> ABI +
      • out-of-line –> API +
  • ABI Vs. API

    • ABI

      • 直接和 二进制文件交互(lib)
      • eg:

        • 直接调用动态库
    • API

      • 需要经过编译器
      • 更快
      • eg:

        • 调用 .c 文件
        • 把 .c 文件中内容,完全放入 build.py 文件中(out-of-line 模式)
        • 调用 C 标准库

          • 设置 函数接口 ffibuilder.cdef("c_code")

            • 函数声明,用于 生成 python wrapper
            • python 和 C 共享的部分
          • 设置 函数实现 ffibuilder.set_source("_name", "c_code", libraries=[])

            • 在 c_code 中调用 标准库
            • 类似个人 C header only 库
  • in-line Vs. out-of-line

    • in-line

      • 每次导入时,处理
      • 不添加外部 .c 文件
      • eg:

        • 直接在 python 中通过 cffi 写 C 代码

          1. 直接使用 ffi.cdef(c_code) 定义类型
          2. var = ffi.new() 定义变量
          3. 使用变量, var[0] = 3
        • ffi.dlopen(None)

          • 直接调用标准库
    • out-of-line

      • 特殊处理,例如编译
      • eg:

        • 把 .c 文件中内容,完全放入 build.py 文件中(API 模式)

          • 自动生成 .c 文件,再编译成模块
  • 混合模式

    • out-of-line, ABI level

      • 参考:out-of-line-abi-level
      • ffibuilder.set_source("name", None)
      • ffibuilder.cdef("c_code")
      • 作用

        • 不需要使用 compiler
        • 降低导入时间
        • 生成 module_name.py 模块文件, 而不是动态库文件
      • 和 ABI 模式类似,易崩溃
    • in-line, API level

      • depricated, 过时的模式,不可用

调用 C 库文件

  • 作用

    • 调用已经存在的库 lib 中的 C 函数
  • 适用范围

    • 静态库
    • 动态库

使用逻辑流程

编写 wrapper 文件
  • 用途

    • 声明函数(签名)
    • 指定相关源程序

编译
  • python build_ext.py

    • 无 .c 文件

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      from cffi import FFI
      ffibuilder = FFI()
      
      # cdef() expects a single string declaring the C types, functions and
      # globals needed to use the shared object. It must be in valid C syntax.
      ffibuilder.cdef("""
          float pi_approx(int n);     # 函数接口声明, C 语言语法
      """)
      
      # set_source() gives the name of the python extension module to
      # produce, and some C source code as a string.  This C code needs
      # to make the declarated functions, types and globals available,
      # so it is often just the "#include".
      ffibuilder.set_source("_pi_cffi", # 输出 .so 文件(模块)名
      """
           #include "pi.h"   // the C header of the library  ;; 头文件
      """,
           libraries=['piapprox'])   # library name, for the linker  ;; 链接库
      
      if __name__ == "__main__":
          ffibuilder.compile(verbose=True)
    • 有 .c 文件

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      
      from cffi import FFI
      ffibuilder = FFI()
      
      ffibuilder.cdef("float pi_approx(int n);")
      
      ffibuilder.set_source("_pi",  # name of the output C extension ;;生成的模块名
      """                           # 包含的头文件
          #include "pi.h"
      """,
          sources=['pi.c'],   # includes pi.c as additional sources  ;; 对应的 .c 文件
          libraries=['m'])    # on Unix, link with the math library  ;; 链接的 lib 名称
      
      if __name__ == "__main__":
          ffibuilder.compile(verbose=True)
调用
  • 通过 module_name.lib.function() 调用

    1
    2
    
    from _pi_cffi import ffi, lib
    print(lib.pi_approx(5000))

embeding

  • 参考

  • 功能

    • C 调用 Python
  • 实现机理

    • python 代码被打包成 .so 文件(动态库)

      • 中间文件

        • .c 文件 内部包含一个同名的 Python 模块

          • 可以静态连接到自己的程序中
    • C 调用 .c 或 .so 文件
  • 实现流程

    • 假设 要实现的结果是 plugin.~~~.so 动态库
    • 实现文件

      • plugin.h

        • 用于规定暴露的接口,函数,类型(struct 等),全局变量
      • cffi_builder.py

        • 用于编译生成 .c 文件和 .so 文件
        • 相关步骤
        • ffibuilder.embeding_api(c_source: str)

          • 作用:规定 .so 遵守的 api 接口
          • 要暴露的 c 接口
          • 类似 头文件内容
          • 解释的结果,被放入生成的 .c 文件
        • ffibuilder.embedding_init_code(python_code)

          • 作用:相 .so 文件 添加 python 代码
          • .so 文件初始化时,加载的 python 代码
          • 这些代码会被(copy)放入 .so 文件

            • 在 Python 初始化完成后,立刻加载
            • 这些代码,相当于 python 的 builtin 模块
          • eg:

            1
            2
            3
            4
            5
            6
            7
            8
            
              ffibuilder.embedding_init_code("""
                  from my_plugin import ffi
            
                  @ffi.def_extern()
                  def do_stuff(p):
                      print("adding %d and %d" % (p.x, p.y))
                      return p.x + p.y
              """)
            • 注意:

              • 这里 my_plugin 是 set_source(module_name, c_code:str)的 第一个参数 module_name
              • ffi.def_extern

                • 用于绑定 api 函数,对应的 python 实现
        • ffibuilder.set_source(module_name: str, c_code: str)

          • 设置 .so 内部模块名

            • 与 ffibuilder.embedding_init_code, 导入模块相呼应
          • 作用:

            • 向生成的 .so 内部添加 C 代码

              • 添加头文件
              • 定义全局变量
        • ffibuilder.compile(target="plugin-1.5.*", verbose=True)

          • 作用:设置编译参数
          • target: 设置 .so 文件名称
    • 相关函数

      • ffibuilder.emit_c_code("my_plugin.c")
      • ffibuilder.emit_python_code("my_plugin.py")

Windows 兼容

  • ffibuilder.embedding_api(c_source: str)

    • 不需要 CFFI_DLLEXPORT int do_stuff(point_t *);
    • 而是使用 extern int do_stuff(point_t *);int do_stuff(point_t *);
  • ffibuilder.set_source(module_name: str, c_code: str)

    • 使用 include, 使用 CFFI_DLLEXPORT int do_stuff(point_t *);
    • eg:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        #ifndef CFFI_DLLEXPORT
        #  if defined(_MSC_VER)
        #    define CFFI_DLLEXPORT  extern __declspec(dllimport)
        #  else
        #    define CFFI_DLLEXPORT  extern
        #  endif
        #endif
      
        CFFI_DLLEXPORT int do_stuff(point_t *);