cffi ---- Python C API Tool
文章目录
使用模式
综述
- 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 代码
- 直接使用
ffi.cdef(c_code)定义类型 var = ffi.new()定义变量- 使用变量,
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 文件
用途
- 声明函数(签名)
指定相关源程序
两种情况
有 .c 文件
例子
特点
指定文件
- .c 文件, eg: pi.c
- .h 文件, eg: pi.h
- 链接的 lib 名称, eg: m (math.h)
无 .c 文件,即使用已有的动态库
例子
特点
指定文件
- .h 文件, eg: pi.h
- 链接的 lib 名称, eg: pi_lib
编译
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 21from 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 14from 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 2from _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 8ffibuilder.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 *);
- 使用 include, 使用
文章作者
上次更新 2022-03-07 (de34a70)