CFFI能够在四种模式中使用:“ABI”和“API”级别,每种模式都有 in-line 或 out- line 准备(或编译)python
ABI模式从二进制级别访问库,而更快的API模式经过C编译器访问库windows
在 in-line 模式中,每次导入Python代码时都会设置全部内容数组
在 out- line 模式中,有一个单独的准备步骤(可能还有C编译),它生成一个模块,主程序能够导入该模块bash
| 版权声明:itisyang,未经博主容许不得转载。app
>>> from cffi import FFI
>>> ffi = FFI()
>>> ffi.cdef(""" ... int printf(const char *format, ...); // copy-pasted from the man page ... """)
>>> C = ffi.dlopen(None) # loads the entire C namespace
>>> arg = ffi.new("char[]", "world") # equivalent to C code: char arg[] = "world";
>>> C.printf("hi there, %s.\n", arg) # call printf
hi there, world.
17 # this is the return value
>>>
复制代码
注意,在Python 3中,须要将byte strings传递给char *参数,在上面的示例中,它将是b“world”和b“hi there, %s!\n”,一般也能够这样 somestring.encode(myencoding)函数
Windows上的Python 3: ffi.dlopen(None)没法工做。这个问题很混乱,并且没法真正解决。若是尝试从系统上存在的特定DLL调用函数,则不会出现问题: 使用ffi.dlopen(“path.dll”)。布局
这个例子不调用任何C编译器。它在所谓的ABI模式下工做,这意味着若是您调用某个函数或访问某个在cdef()中稍有错误声明的结构的某些字段,它就会崩溃。性能
若是使用C编译器安装模块是一个选项,那么强烈建议使用API模式。(它也更快。)测试
from cffi import FFI
ffi = FFI()
ffi.cdef(""" typedef struct { unsigned char r, g, b; } pixel_t; """)
image = ffi.new("pixel_t[]", 800*600)
f = open('data', 'rb') # binary mode -- important
f.readinto(ffi.buffer(image))
f.close()
image[100].r = 255
image[100].g = 192
image[100].b = 128
f = open('data', 'wb')
f.write(ffi.buffer(image))
f.close()
复制代码
您能够调用ffi.new(“pixel_t[600][800]”)得到一个二维数组。ui
# file "example_build.py"
# Note: we instantiate the same 'cffi.FFI' class as in the previous
# example, but call the result 'ffibuilder' now instead of 'ffi';
# this is to avoid confusion with the other 'ffi' object you get below
from cffi import FFI
ffibuilder = FFI()
ffibuilder.set_source("_example",
r""" // passed to the real C compiler, // contains implementation of things declared in cdef() #include <sys/types.h> #include <pwd.h> // We can also define custom wrappers or other functions // here (this is an example only): static struct passwd *get_pw_for_root(void) { return getpwuid(0); } """,
libraries=[]) # or a list of libraries to link with
# (more arguments like setup.py's Extension class:
# include_dirs=[..], extra_objects=[..], and so on)
ffibuilder.cdef(""" // declarations that are shared between Python and C struct passwd { char *pw_name; ...; // literally dot-dot-dot }; struct passwd *getpwuid(int uid); // defined in <pwd.h> struct passwd *get_pw_for_root(void); // defined in set_source() """)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
复制代码
您须要运行example_build.py脚本一次生成“source code”到文件_example.c中。并将其编译为一个常规的c扩展模块。(CFFI根据set_source()的第二个参数是否为None,选择生成Python或C模块。)
这一步须要一个C编译器。它生成一个名为 _example.so 或 _example.pyd 的文件。若是须要,它能够像其余扩展模块同样以预编译的形式发布。
在主程序中使用:
from _example import ffi, lib
p = lib.getpwuid(0)
assert ffi.string(p.pw_name) == b'root'
p = lib.get_pw_for_root()
assert ffi.string(p.pw_name) == b'root'
复制代码
注意,passwd 结构体是独立精确的C布局结构(它是“API级别”,而不是“ABI级别”),须要一个C编译器才能运行 example_build.py 。
还要注意,在运行时,API模式比ABI模式快。
把这个模块使用Setuptools集成到setup.py中:
from setuptools import setup
setup(
...
setup_requires=["cffi>=1.0.0"],
cffi_modules=["example_build.py:ffibuilder"],
install_requires=["cffi>=1.0.0"],
)
复制代码
若是您想调用一些没有预编译的库,可是有C源代码的库,那么最简单的解决方案是建立一个单独的扩展模块,该模块由这个库的C源代码和额外的CFFI包装器编译而成。例如,假设您从文件 pi.c 和 pi.h 开始:
/* filename: pi.c*/
# include <stdlib.h>
# include <math.h>
/* Returns a very crude approximation of Pi
given a int: a number of iteration */
float pi_approx(int n){
double i,x,y,sum=0;
for(i=0;i<n;i++){
x=rand();
y=rand();
if (sqrt(x*x+y*y) < sqrt((double)RAND_MAX*RAND_MAX))
sum++; }
return 4*(float)sum/(float)n; }
复制代码
/* filename: pi.h*/
float pi_approx(int n);
复制代码
建立一个名为 pi_extension_build.py 的脚本,构建C扩展:
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
libraries=['m']) # on Unix, link with the math library
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
复制代码
构建扩展:
python pi_extension_build.py
复制代码
能够发现,在工做目录中,生成的输出文件: _pi.c, _pi.o 和编译后的C扩展(例如Linux上生成 _pi.so )。它能够从Python调用:
from _pi.lib import pi_approx
approx = pi_approx(10)
assert str(pi_approximation).startswith("3.")
approx = pi_approx(10000)
assert str(approx).startswith("3.1")
复制代码
out-of-line ABI 模式是 常规(API)out-of-line模式和in-line ABI 的混合,优势是不须要C编译器),缺点是更容易崩溃。
这种混合模式能够大大减小导入时间,由于解析大型C头文件很慢。它还容许您在构建时进行更详细的检查,而没必要担忧性能。
# file "simple_example_build.py"
from cffi import FFI
ffibuilder = FFI()
ffibuilder.set_source("_simple_example", None)
ffibuilder.cdef(""" int printf(const char *format, ...); """)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
复制代码
运行一次会产生_simple_example.py,你的主程序只导入生成的模块,而再也不须要 simple_example_build.py
from _simple_example import ffi
lib = ffi.dlopen(None) # Unix: open the standard C library
#import ctypes.util # or, try this on Windows:
#lib = ffi.dlopen(ctypes.util.find_library("c"))
lib.printf(b"hi there, number %d\n", ffi.cast("int", 2))
复制代码
注意,ffi.dlopen()不会调用任何额外的方法来定位库,这意味着 ffi.dlopen(“libfoo.so”)是能够的,可是ffi.dlopen(“foo”)不行。 在后一种状况下,您能够将其替换为ffi.dlopen(ctypes.util.find_library(“foo”))。而且,None 只能在Unix打开C标准库。
为了便于分发,你能够把它静态地包含在你的项目的源文件中,使用Setuptools在setup.py这样编写:
from setuptools import setup
setup(
...
setup_requires=["cffi>=1.0.0"],
cffi_modules=["simple_example_build.py:ffibuilder"],
install_requires=["cffi>=1.0.0"],
)
复制代码
在二进制级别 (“ABI”) 访问C库充满了问题,尤为是在非 windows 平台上。
ABI 级别最直接的缺点是,调用函数须要通过很是通用的 libffi 库,它很慢(并且老是不能在非标准平台上完美经过测试)。API模式编译一个直接调用目标函数的 CPython C 包装器。相对而言,它的速度要快得多(并且运行得比 libffi 更好)。
选择API模式的更基本缘由是,C库一般与C编译器一块儿使用。你不该该作诸如猜想结构中字段的位置之类的事情。上面的“真实示例”展现了CFFI如何在底层使用C编译器:示例使用 set_source(..., "C source...") 而不是 dlopen()。当使用这种方法时,咱们有一个优点,咱们能够在 cdef() 的不一样地方使用字面上的 “……” ,缺失的信息将在C编译器的帮助下完成。CFFI 将把它转换为单个C源文件,其中包含未修改的 “C source” 部分,后面跟随着一些特殊C代码和 cdef() 派生的声明。当编译这个C文件时,生成的C扩展模块将包含咱们须要的全部信息。就像往常同样,若是咱们错误地声明了某个函数的签名,C编译器将给出警告或错误。
注意,set_source()中的 “C source” 部分能够包含任意的C代码。您可使用它来声明一些用c语言编写的辅助函数。(你能够在“C source”部分,使用static C关键字)
例如,这能够用来将宏包装成更标准的C函数。额外的C层在其余方面也颇有用,好比调用函数,这些函数须要一些复杂的参数结构,您更喜欢在C中构建而不是在Python中。(另外一方面,若是您须要调用 “function-like” 宏,那么您能够直接在cdef()中声明它们,就好像它们是函数同样。)