cffi:C 外部函数接口
使用 cffi 和内联 C 声明,以更符合 Python 风格的方式实现 C 互操作。
cffi:C 外部函数接口 是 CoddyKit 上的免费 Python Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。
什么是 cffi
cffi(C 外部函数接口)允许您使用 C 声明而不是 ctypes 类从 Python 调用 C 代码。它更符合 Python 风格,而且通常更安全。
# pip install cffi
from cffi import FFI
ffi = FFI()
ffi.cdef("double sqrt(double x);")
libm = ffi.dlopen(None) # current process
print(libm.sqrt(2.0)) # 1.4142...ABI 模式与 API 模式
ffi.dlopen() 属于 ABI 模式,会在运行时调用现有的共享库。API 模式会编译 C 包装器,因此速度更快,也更稳健。
from cffi import FFI
ffi = FFI()
# ABI mode (no compile step):
ffi.cdef("int abs(int x);")
libc = ffi.dlopen("libc.so.6") # Linux
print(libc.abs(-42)) # 42cdef 声明
请使用 ffi.cdef() 声明希望从 Python 使用的 C 函数签名、结构体和类型别名。
from cffi import FFI
ffi = FFI()
ffi.cdef("""
typedef struct {
double x;
double y;
} Point;
double distance(Point a, Point b);
""")
# lib = ffi.dlopen("mylib.so")创建 C 结构体
请使用 ffi.new() 分配 C 结构体,并从 Python 设置其字段。
from cffi import FFI
ffi = FFI()
ffi.cdef("typedef struct { int x; int y; } Vec2;")
lib = ffi.dlopen(None)
v = ffi.new("Vec2 *")
v.x = 3
v.y = 4
print(v.x, v.y) # 3 4字符串和缓冲区
请使用 ffi.new("char[]", n) 创建可变缓冲区,并使用 ffi.string(ptr) 将 C 字符串指针转换为 Python 字节串。
from cffi import FFI
ffi = FFI()
buf = ffi.new("char[]", b"hello world")
print(ffi.string(buf)) # b"hello world"
# Mutable buffer for output:
out = ffi.new("char[128]")
# c_func(out, 128) # fill the buffer
# result = ffi.string(out).decode()使用 verify 的 API 模式
API 模式会编译一个小型 C 包装模块,从而提升类型安全性和性能。请使用 ffi.set_source() 并在离线环境中构建。
from cffi import FFI
ffi = FFI()
ffi.cdef("int add(int a, int b);")
ffi.set_source("_mylib",
"""
int add(int a, int b) { return a + b; }
"""
)
# Build: python build_mylib.py
# from _mylib import ffi, lib
# print(lib.add(3, 4)) # 7从 C 到 Python 的回调
请使用 ffi.callback() 将 Python 函数包装为 C 函数指针,以便将其传递给 C 库。
from cffi import FFI
ffi = FFI()
ffi.cdef("typedef int (*compare_fn)(int, int);")
@ffi.callback("int(int, int)")
def compare(a, b):
return a - b
# pass compare to a C sort function内存管理
使用 ffi.new() 分配的对象由 Python 所有,并会自动释放。请使用 ffi.gc(ptr, destructor) 附加自定义析构函数。
from cffi import FFI
ffi = FFI()
# ptr freed when Python GC collects it:
buf = ffi.new("int[10]")
buf[0] = 42
print(buf[0]) # 42
# Custom destructor:
# ptr = ffi.gc(lib.alloc(), lib.free)cffi 与 ctypes
cffi 使用 C 声明字符串(对 C 开发者来说更自然),而 ctypes 使用 Python 类。cffi 的 API 模式速度更快;ctypes 无需编译。
# ctypes approach:
import ctypes
libc = ctypes.CDLL("libc.so.6")
libc.abs.restype = ctypes.c_int
libc.abs.argtypes = [ctypes.c_int]
print(libc.abs(-5)) # 5
# cffi approach:
from cffi import FFI; ffi = FFI()
ffi.cdef("int abs(int);")
lib = ffi.dlopen(None)
print(lib.abs(-5)) # 5将 cffi 与 NumPy 搭配使用
请通过 cffi 使用 ffi.cast() 和数组的 ctypes.data 指针,将 NumPy 数组数据直接传递给 C。
import numpy as np
from cffi import FFI
ffi = FFI()
arr = np.array([1.0, 2.0, 3.0])
ptr = ffi.cast("double *", arr.ctypes.data)
# Now ptr can be passed to a C function expecting double*使用 cffi 处理错误
C 函数通常通过返回值表示错误。请检查返回值,并使用 ffi.errno 获取 POSIX 错误代码。
import errno as errno_mod
from cffi import FFI
ffi = FFI()
ffi.cdef("int open(const char *path, int flags);")
lib = ffi.dlopen(None)
fd = lib.open(b"/nonexistent", 0)
if fd == -1:
err = ffi.errno
raise OSError(err, "open failed")快速检查
cffi 使用什么方法来声明您要调用的 C 函数签名?
总结
cffi 使用 ffi.cdef() 声明 C 函数签名,并使用 ffi.dlopen() 进行 ABI 模式的库调用。API 模式会编译包装器,以获得更好的性能。请使用 ffi.new() 分配 C 内存,并使用 ffi.callback() 创建从 Python 到 C 的回调。
常见问题解答
「cffi:C 外部函数接口」课时是免费的吗?
是的 — 「cffi:C 外部函数接口」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。
「cffi:C 外部函数接口」这节课中我会学到什么?
使用 cffi 和内联 C 声明,以更符合 Python 风格的方式实现 C 互操作。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Python Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「cffi:C 外部函数接口」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Python Academy 课中编写并运行代码吗?
能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。