CFFI 参考¶
Contents
- CFFI 参考
- FFI 接口
- ffi.NULL
- ffi.error
- ffi.new()
- ffi.cast()
- ffi.errno, ffi.getwinerror()
- ffi.string(), ffi.unpack()
- ffi.buffer(), ffi.from_buffer()
- ffi.memmove()
- ffi.typeof(), ffi.sizeof(), ffi.alignof()
- ffi.offsetof(), ffi.addressof()
- ffi.CData, ffi.CType
- ffi.gc()
- ffi.new_handle(), ffi.from_handle()
- ffi.dlopen(), ffi.dlclose()
- ffi.new_allocator()
- ffi.release() and the context manager
- ffi.init_once()
- ffi.getctype(), ffi.list_types()
- 转换
- FFI 接口
FFI 接口¶
这个页面记录了运行时接口的两种类型 "FFI" 和 "CompiledFFI"。 这两种类型彼此非常相似。 如果导入out-of-line模块,则会获得CompiledFFI对象。 您从显式编写cffi.FFI()获得FFI对象。 与CompiledFFI不同,FFI类型还记录了其他方法在 下一页.
ffi.new()¶
ffi.new(cdecl, init=None):
根据指定的C语言类型分配实例并返回指向它的指针。 指定的C语言类型必须是指针或数组: new('X *')
分配一个X并返回一个指向它的指针,而 new('X[10]')
分配一个10个X的数组并返回一个引用它的数组 (它主要像指针一样工作,就像在C中一样).
您还可以使用 new('X[]', n)
来分配非常数长度为n的数组。 请参阅其他有效初始化值的 详细文档。
当返回的 <cdata>
对象超出范围时,将释放内存。 换句话说,返回的 <cdata>
对象拥有它指向的类型 cdecl
值的所有权。 这意味着只要此对象保持活动状态,就可以使用原始数据,但不能长时间使用。 将指针复制到其他地方的内存时要小心,例如 进入另一个结构。
另外,这意味着像 x = ffi.cast("B *", ffi.new("A *"))
或 x = ffi.new("struct s[1]")[0]
这样的一行是错误的: 新分配的对象超出范围即刻,因此立即被释放,x
是垃圾变量。 唯一可以这样做的是,对于指向结构体的指针和指向联合类型的指针,有一种特殊情况: 在 p = ffi.new("struct-or-union *", ..)
之后, 然后 p
or p[0]
使内存保持活动状态。
在应用可选的初始化值之前,返回的内存最初被清除(用零填充)。 有关性能,请参阅 ffi.new_allocator() 以获取分配非零初始化内存的方法。
版本1.12中的新功能: 另见 ffi.release()
。
ffi.cast()¶
ffi.cast("C type", value): 类似于C语言转换(cast): 返回使用给定值初始化的命名C语言类型的实例。 该值在任何类型的整数或指针之间转换。
ffi.errno, ffi.getwinerror()¶
ffi.errno: 从此线程中最近的C语言调用收到的 errno
的值,并传递给后面的C语言调用。 (这是一个线程本地读写属性。)
ffi.getwinerror(code=-1): 在Windows上,除了 errno
之外,我们还在函数调用中保存和恢复 GetLastError()
值。 此函数将此错误代码作为元组 (code,message)
返回,在引发WindowsError时添加类似Python的可读消息。 如果给出参数 code
, 则将该代码格式化为消息而不是使用 GetLastError()
。
(则将该代码格式化为消息而不是使用 GetLastError()
函数)
ffi.string(), ffi.unpack()¶
ffi.string(cdata, [maxlen]): 从'cdata'返回一个Python字符串(或unicode字符串)。
- 如果'cdata'是一个指针或字符数组或字节,则返回以null结尾的字符串。 返回的字符串将一直延伸到第一个空字符。 'maxlen'参数限制我们查找空字符的距离。 如果'cdata'是一个数组,那么'maxlen'默认为它的长度。 请参阅下面的
ffi.unpack()
以获取继续经过第一个空字符的方法。 Python 3: 这会返回一个bytes
,而不是str
. - 如果'cdata'是wchar_t的指针或数组,则返回遵循相同规则的unicode字符串。 版本1.11中的新功能: 可以是char16_t或char32_t。
- 如果'cdata'是单个字符或字节或wchar_t或charN_t,则将其作为字节字符串或unicode字符串返回。 (请注意,在某些情况下,单个wchar_t或char32_t可能需要长度为2的Python unicode字符串。)
- 如果'cdata'是枚举,则将枚举数的值作为字符串返回。如果该值超出范围,则只返回字符串整数。
ffi.unpack(cdata, length): 解包给定长度的C语言数据数组,返回Python字符串/ unicode/list。 'cdata'应该是一个指针;如果是数组,则首先将其转换为指针类型。 版本1.6中的新功能。
- 如果'cdata'是指向'char'的指针,则返回一个字节字符串。它不会在第一个空值处停止。 (一个等效的方法是
ffi.buffer(cdata, length)[:]
。) - 如果'cdata'是指向'wchar_t'的指针,则返回一个unicode字符串。 ('length'以wchar_t的数量来衡量;它不是以字节为单位的大小。) 版本1.11中的新功能: 也可以是char16_t或char32_t。
- 如果'cdata'是指向其他任何内容的指针,则返回给定'length'的列表。 (一个较慢的方法是
[cdata[i] for i in range(length)]
。)
ffi.buffer(), ffi.from_buffer()¶
ffi.buffer(cdata, [size]): 返回一个缓冲区对象,该对象引用给定'cdata'指向的原始C数据,其长度为'size'字节。 Python调用"一个缓冲区",或者更准确地说是"支持缓冲区接口的对象",是一个表示一些原始内存的对象,可以传递给各种内置或扩展函数;这些内置函数可以读取或写入原始内存直接,无需额外的拷贝。
'cdata' 参数必须是指针或数组。 如果未指定,缓冲区的大小是 cdata
指向的大小,或者数组的整个大小。
以下是buffer()有用的几个示例:
- 使用
file.write()
和file.readinto()
与这样的缓冲区 (对于以二进制模式打开的文件) - 覆盖结构的内容: 如果
p
是指向一个cdata,使用ffi.buffer(p)[:] = newcontent
,其中newcontent
是一个字节对象 (str
in Python 2)。
请记住,就像在C语言中一样,您可以使用 array + index
来获取指向数组的索引项的指针。 (在C中你可能更自然地编写
&array[index]
,但这是等价的。)
返回的对象的类型不是内置 buffer
,也不是 memoryview
类型,因为这些类型的API在Python版本中变化太大。
相反,除了支持缓冲区接口之外,它还具有以下Python API (Python 2的 buffer
子集。):
buf[:]
orbytes(buf)
: 将数据复制出缓冲区,返回常规字节字符串 (或buf[start:end]
作为一个部分)buf[:] = newstr
: 将数据复制到缓冲区中 (或buf[start:end] = newstr
)len(buf)
,buf[index]
,buf[index] = newchar
: 作为一系列字符访问。
ffi.buffer(cdata)
返回的缓冲区对象使
cdata
对象保持活动状态: 如果它最初是一个拥有的cdata,那么只要缓冲区处于活动状态,它的拥有内存就不会被释放。
Python 2/3兼容性说明: 你应该避免使用 str(buf)
,
因为它在Python 2和Python 3之间产生不一致的结果。
(这类似于 str()
在常规字节字符串上给出不一致的结果)。 请改用 buf[:]
。
版本1.10中的新功能: ffi.buffer
此时是返回的缓冲区对象的类型; ffi.buffer()
实际上调用了构造函数。
ffi.from_buffer([cdecl,] python_buffer, require_writable=False):
返回一个cdata数组 (默认情况下为 <cdata 'char[]'>
),指向给定Python对象的数据,该对象必须支持缓冲区接口。 请注意, ffi.from_buffer()
将通用Python缓冲区对象转换为cdata对象,而 ffi.buffer()
执行相反的转换。 两个调用实际上都不会复制任何数据。
ffi.from_buffer()
用于包含大量原始数据的对象,如 字节数组(bytearrays)
或 array.array
或 numpy数组。 它支持旧的 缓冲区 API (在Python 2.x中) 和新的 memoryview API。 请注意,如果传递只读缓冲区对象,则仍会获得常规 <cdata 'char[]'>
; 如果原始缓冲区不希望您这样做,那么您有责任不在那里写。
特别是,永远不要修改字节串!
只要 ffi.from_buffer()
返回的cdata对象处于活动状态,原始对象就会保持活动状态 (并且在内存视图的情况下被锁定)。
一个常见的用例是调用一个带有一些 的c函数,该 char *
指向一个python对象的内部缓冲区; 对于这种情况,您可以直接将 ffi.from_buffer(python_buffer)
作为参数传递给调用。
版本1.10中的新功能: python_buffer
可以是支持buffer/memoryview接口的任何东西 (unicode字符串除外)。 以前,1.7版本版本支持bytearray对象 (小心,如果你调整bytearray的大小 <cdata>
对象将指向释放的内存); 版本1.8及以上版本支持字节字符串。
版本1.12中的新功能: 添加了可选的第 一个 参数 cdecl
和关键字参数 require_writable
:
cdecl
默认为"char[]"
,但是可以为结果指定不同的数组或(从1.13版本开始)指针类型。 像"int[]"
这样的值将返回一个整数数组而不是字符, 其长度将设置为适合缓冲区的整数数。 (如果划分不准确,则向下舍入)。 像"int[42]"
或"int[2][3]"
这样的值将返回一个正好为42(相应的2乘3)整数的数组, 如果缓冲区太小则会引发ValueError。 指定"int[]"
和使用旧代码p1 = ffi.from_buffer(x); p2 = ffi.cast("int *", p1)
间的区别在于, 只要p2
在使用,旧代码就需要保持p1
活动, 因为只有p1
保持底层python对象活动和锁定。 (另外,ffi.from_buffer("int[]", x)
提供了更好的数组绑定检查。)版本1.13中的新功能:
cdecl
可以是指针类型。 果它指向一个结构或联合,则可以像往常一样编写p.field
而不是p[0].field
。 您也可以访问p[n]
; n请注意,在这种情况下,CFFI不会执行任何边界检查。 还要注意,p[0]
不能用于保持缓冲区活动 (不像ffi.new()
)。如果
require_writable
设置为True,则如果从python_buffer
得的缓冲区是只读的 (例如,如果python_buffer
是字节字符串)。 则函数将失败。确切的异常是由对象本身引发的,对于字节这样的东西,它随Python版本而变化,所以不要依赖它。 (在版本1.12之前,使用修改可以实现相同的效果: 调用ffi.memmove(python_buffer, b"", 0)
。 如果对象是可写的,这没有效果,但如果它是只读的,则会失败。) 请记住,CFFI没有实现C关键字const
: 即使您将require_writable
显式设置为False,您仍然会得到常规的读写cdata指针。
版本1.12中的新功能: 另见 ffi.release()
。
ffi.memmove()¶
ffi.memmove(dest, src, n): 将 n
个字节从内存区域
src
复制到内存区域 dest
。 见下面的例子。 受C函数 memcpy()
和 memmove()
的启发————就像后者一样,这些区域可以重叠。 dest
和 src
中的每一个都可以是cdata指针,也可以是支持buffer/memoryview接口的python对象。
在 dest
的情况下,buffer/memoryview 必须是可写的。
版本1.3中的新功能。 例:
ffi.memmove(myptr, b"hello", 5)
将b"hello"
的5个字节复制到myptr
指向的区域。ba = bytearray(100); ffi.memmove(ba, myptr, 100)
将100个字节从myptr
复制到bytearrayba
中。ffi.memmove(myptr + 1, myptr, 100)
将100个字节从myptr
的内存移到myptr + 1
的内存。
在1.10之前的版本中,ffi.from_buffer()
对缓冲区的类型有限制,这使得 ffi.memmove()
更加通用。
ffi.typeof(), ffi.sizeof(), ffi.alignof()¶
ffi.typeof("C type" or cdata object): 返回与解析后的字符串对应的
<ctype>
类型的对象,或者返回cdata实例的C语言类型。通常,您不需要调用此函数或在代码中显式操作 <ctype>
对象: 任何接受C类型的地方都可以接收字符串或预先解析的 ctype
对象 (由于字符串的缓存,因此没有真正的性能差异)。 它在编写类型检查时仍然有用,
例如:
def myfunction(ptr):
assert ffi.typeof(ptr) is ffi.typeof("foo_t*")
...
还要注意,从字符串 "foo_t*"
到
<ctype>
对象的映射存储在一些内部字典中。 这样可以确保只有一个 <ctype 'foo_t *'>
对象,因此您可以使用 is
运算符来比较它。 缺点是字典项目现在是唯一的。 将来,我们可能会添加易懂的改造旧未使用的旧条目。 同时,请注意,如果使用许多不同长度的字符串(如 "int[%d]" % length
)来命名类型,则会创建许多不唯一的缓存项。
ffi.sizeof("C type" or cdata object): 以字节为单位返回参数的大小。 参数可以是C类型,也可以是cdata对象,就像C语言中等效的 sizeof
算符一样。
对于 array = ffi.new("T[]", n)
,然后 ffi.sizeof(array)
返回
n * ffi.sizeof("T")
. 版本1.9中的新功能: 类似的规则适用于末尾具有可变大小数组的结构。更准确地说,如果
p
由 ffi.new("struct foo *", ...)
返回,则
ffi.sizeof(p[0])
此时返回总分配大小。 在以前的版本中,它只用于返回 ffi.sizeof(ffi.typeof(p[0]))
,这是忽略可变大小部分的结构的大小。 (请注意,由于对齐, ffi.sizeof(p[0])
可能返回小于 ffi.sizeof(ffi.typeof(p[0]))
的值。)
ffi.alignof("C type"): 返回参数的自然对齐大小(以字节为单位)。 对应于GCC中的 __alignof__
运算符。
ffi.offsetof(), ffi.addressof()¶
ffi.offsetof("C struct or array type", *fields_or_indexes): 返回给定字段结构中的偏移量。对应于C语言中的 offsetof()
。
在嵌套结构的情况下,您可以给出几个字段名称。 在指针或数组类型的情况下,您还可以提供与数组项对应的数值。 例如, ffi.offsetof("int[5]", 2)
等于两个整数的大小,也是如此。 ffi.offsetof("int *", 2)
。
ffi.addressof(cdata, *fields_or_indexes): 相当于C语言中的 '&'运算符:
ffi.addressof(<cdata 'struct-or-union'>)
返回一个cdata,它是指向此结构或联合的指针。 返回的指针只有是原始的cdata
对象才有效;如果它是直接从ffi.new()
获得的,请确保它保持活动状态。
2. ffi.addressof(<cdata>, field-or-index...)
返回给定结构或数组中的字段或数组项的地址。 对于嵌套结构或数组,您可以提供多个字段或索引以递归查看。 注意,ffi.addressof(array, index)
也可以表示为 array + index
: 在CFFI和C中都是如此,其中 &array[index]
只是 array + index
。
3. ffi.addressof(<library>, "name")
从给定的库对象返回指定函数或全局变量的地址。
对于函数,它返回一个包含指向函数的指针的常规cdata对象。
请注意,案例1. 不能用于获取原始或指针的地址,而只能用于获取结构或联合。
实现起来很困难,因为只有结构和联合在内部存储为数据的间接指针。 如果你需要一个可以获取地址的C语言int,首先使用 ffi.new("int[1]")
; 同样,对于指针,使用 ffi.new("foo_t *[1]")
。
ffi.CData, ffi.CType¶
ffi.CData, ffi.CType: 在本文档的其余部分中称为 <cdata>
和 <ctype>
的对象的Python类型。请注意,某些cdata对象实际上可能是 ffi.CData
的子类, 并且与ctype类似, 因此您应该检查 if isinstance(x, ffi.CData)
。 此外, <ctype>
对象具有许多内建属性: kind
和 cname
总是存在,根据它们的类型,它们也可能有
item
, length
, fields
, args
, result
, ellipsis
,
abi
, elements
和 relements
。
版本1.10中的新功能: ffi.buffer
现在也是 一种类型。
ffi.gc()¶
ffi.gc(cdata, destructor, size=0):
返回指向相同数据的新cdata对象。 稍后,当这个新的cdata对象被垃圾收集时,将调用
destructor(old_cdata_object)
。 用法示例:
ptr = ffi.gc(lib.custom_malloc(42), lib.custom_free)
.
请注意, ffi.new()
返回类似的对象,返回的指针对象具有所有权,这意味着只要这个确切的返回对象被垃圾收集,就会调用析构函数。
版本1.12中的新功能: 另见 ffi.release()
。
ffi.gc(ptr, None, size=0):
删除对常规调用 ffi.gc
返回的对象的所有权,并且在垃圾收集时不会调用析构函数。 该对象在本地修改,并且该函数返回 None
。 版本1.7中的新功能: ffi.gc(ptr, None)
请注意,对于有限的资源应该避免使用 ffi.gc()
,或者 (cffi低于1.11) 用于大内存分配。 在PyPy上尤其如此: 它的GC不知道返回的 ptr
有多少内存或多少资源。 只有在分配了足够的内存时,它才会运行GC (因此可能比你预期的更晚地运行析构函数)。 而且,析构函数在PyPy当时的任何线程中被调用,这对于某些C语言库来说可能是一个问题。 在这些情况下,请考虑使用自定义 __enter__()
和 __exit__()
方法编写包装类,在已知时间点分配和释放C语言数据,并在 with
语句中使用它。 在cffi 1.12中,另见 ffi.release()
。
版本1.11中的新功能: size
参数。 如果给定,这应该是 ptr
保持活动的大小(以字节为单位)的估计值。 该信息被传递给垃圾收集器,解决了上述问题的一部分。 size
参数在PyPy上最为重要; 在CPython上,到目前为止它被忽略了,但是将来它也可以用来更友好地触发循环引用GC (参见 CPython
问题 31105)。
可以使用负 size
调用 ffi.gc(ptr, None, size=0)
,以撤销估量。 但这不是强制性的:
如果大小估计不匹配,则不会有任何不同步。 它只会使得下一次GC开始或多或少提前开始。
请注意,如果您有多个 ffi.gc()
对象,则将以随机顺序调用相应的析构函数。 如果您需要特定顺序,参见 问题 340 的讨论。
ffi.new_handle(), ffi.from_handle()¶
ffi.new_handle(python_object): 返回 void *
类型的非NULL cdata,其中包含对 python_object
的不透明引用。 您可以将其传递给C函数或将其存储到C语言结构中。 稍后,您可以使用 ffi.from_handle(p) 从具有相同 void *
指针的值中检索原始 python_object
。
调用 ffi.from_handle(p) 无效,如果 new_handle() 返回的cdata对象未保持活动状态,则可能会崩溃!
请参阅下面的 典型用法示例。
(如果你想知道,这个 void *
是不是 PyObject *
指针。 无论如何,这对PyPy没有意义。)
ffi.new_handle()/from_handle()
函数在 概念 上的工作方式如下:
new_handle()
返回包含Python对象引用的cdata对象; 我们将它们统称为"句柄"cdata对象。 这些句柄cdata对象中的void *
值是随机的但是唯一的。from_handle(p)
搜索所有实时"句柄"cdata对象,以获得与其void *
值具有相同值p
的对象。 然后它返回该句柄cdata对象引用的Python对象。 如果没有找到,则会出现"未定义的行为" (即崩溃)。
"句柄"cdata对象使Python对象保持活动状态,类似于 ffi.new()
返回一个使一块内存保持活动状态的cdata对象。 如果句柄cdata对象本身不再存在,则关联 void * -> python_object
将失效,而
from_handle()
将崩溃。
版本1.4中的新功能: 对 new_handle(x)
的两次调用保证返回具有不同 void *
值的cdata对象,即使使用相同的 x
也是如此。 这是一个有用的功能,可以避免以下技巧中出现意外重复的问题: 如果你需要保持“句柄”,直到明确要求释放它,但没有一个自然的Python端附加它,那么最简单的是将它 add()
到一个全局集合。 稍后可以通过
global_set.discard(p)
稍后可以通过 p
为任何cdata对象,其 void *
值比较相等。
用法示例: 假设你有一个C语言库,你必须调用一个
lib.process_document()
函数来调用一些回调。 process_document()
函数接收指向回调和 void *
参数的指针。 然后使用等于提供值的 void
*data
参数调用回调。 在这种典型情况下,您可以像这样实现它 (out-of-line API 模式):
class MyDocument:
...
def process(self):
h = ffi.new_handle(self)
lib.process_document(lib.my_callback, # the callback
h, # 'void *data'
args...)
# 'h' stays alive until here, which means that the
# ffi.from_handle() done in my_callback() during
# the call to process_document() is safe
def callback(self, arg1, arg2):
...
# the actual callback is this one-liner global function:
@ffi.def_extern()
def my_callback(arg1, arg2, data):
return ffi.from_handle(data).callback(arg1, arg2)
ffi.dlopen(), ffi.dlclose()¶
ffi.dlopen(libpath, [flags]): 打开并将"句柄"作为 <lib>
对象返回到动态库。 参见 准备和分发模块。
ffi.dlclose(lib):显式关闭 ffi.dlopen()
返回的 <lib>
对象。
ffi.RLTD_...: 常量: ffi.dlopen()
的标志。
ffi.new_allocator()¶
ffi.new_allocator(alloc=None, free=None, should_clear_after_alloc=True):
返回一个新的分配器。 是一个可调用的,其行为类似于
ffi.new()
,但使用提供的低级 alloc
和 free
函数。 版本1.2中的新功能。
alloc()
是以size作为唯一参数调用的。如果返回空值,则引发MemoryError。 稍后,如果 free
不是None,则将使用 alloc()
的结果作为参数调用它。 两者都可以是Python函数,也可以直接是C语言函数。 如果只有 free
是None,则不调用释放函数。 如果 alloc
和 free
都为None,则使用默认的alloc/free组合。 (换句话说,调用 ffi.new(*args)
等同于 ffi.new_allocator()(*args)
。)
如果 should_clear_after_alloc
设置为False,则假定 alloc()
返回的内存已被清除 (或者你对内存垃圾没问题); 否则CFFI会清除它。 例: 为了提高性能,如果使用 ffi.new()
来分配大内存块,使初始内容保持未初始化状态,则可以执行以下操作:
# at module level
new_nonzero = ffi.new_allocator(should_clear_after_alloc=False)
# then replace `p = ffi.new("char[]", bigsize)` with:
p = new_nonzero("char[]", bigsize)
注意: 以下是一般性警告,特别适用于
(但不仅限于) PyPy 5.6或更早版本 (PyPy > 5.6 尝试说明 ffi.new()
或自定义分配器返回的内存; CPython 使用引用计数)。 如果您进行了大量的分配,那么就无法保证何时释放内存。 如果要确保内存被及时释放 (例如,在分配更多内存之前),则应同时避免 new()
和 new_allocator()()
。
另一种方法是声明并调用C语言 malloc()
和 free()
函数,或者像 mmap()
和 munmap()
这样的变体。 然后,您可以精确地控制分配和释放内存的时间。 例如,
将这两行添加到现有的 ffibuilder.cdef()
:
void *malloc(size_t size);
void free(void *ptr);
然后手动调用这两个函数:
p = lib.malloc(n * ffi.sizeof("int"))
try:
my_array = ffi.cast("int *", p)
...
finally:
lib.free(p)
在cffi版本1.12中,您确实可以使用 ffi.new_allocator()
但是使用
with
语句 (请参阅 ffi.release()
) 来强制在已知点调用释放函数。 以上相当于此代码:
my_new = ffi.new_allocator(lib.malloc, lib.free) # at global level
...
with my_new("int[]", n) as my_array:
...
Warning: 由于存在错误, p = ffi.new_allocator(..)("struct-or-union *")
可能不遵循 p
或 p[0]
使内存保持活动的规则, 该规则适用于普通的 ffi.new("struct-or-union *")
分配器。
在某些情况下,如果仅引用 p[0]
,则会释放内存。 原因是该规则不适用于 ffi.gc()
, 有时会在
ffi.new_allocator()()
的实现中使用; 这可能会在将来的版本中修复。
ffi.release() and the context manager¶
ffi.release(cdata): 从 ffi.new()
,ffi.gc()
,ffi.from_buffer()
或
ffi.new_allocator()()
释放cdata对象持有的资源。 之后不得使用cdata对象。
cdata对象的普通Python析构函数释放相同的资源,但这允许在已知的时间释放,而不是在将来的某个未指定的点释放。
版本1.12中的新功能。
ffi.release(cdata)
相当于 cdata.__exit__()
,这意味着您可以使用 with
语句来确保在块末尾释放cdata。 (在版本1.12及以上):
with ffi.from_buffer(...) as p:
do something with p
效果更为精确,如下所示:
- 对于从
ffi.gc(destructor)
返回的对象,ffi.release()
将导致立即调用destructor
。 - 在自定义分配器返回的对象上,立即调用自定义自由函数。
- 在CPython上,
ffi.from_buffer(buf)
锁定缓冲区,因此可以使用ffi.release()
在已知时间解锁它。 在PyPy上,没有锁定 (到目前为止);ffi.release()
的效果仅限于删除链接,即使cdata对象保持活动状态,也允许对原始缓冲区对象进行垃圾回收。 - 在CPython上,这个方法对
ffi.new()
返回的对象没有影响(到目前为止),因为内存是与cdata对象内联分配的,不能独立释放。 可能会在将来的cffi版本中修复它。 - 在PyPy上,
ffi.release()
立即释放ffi.new()
内存。它很有用,因为否则内存将保持活动状态,直到下一次GC发生。 如果使用ffi.new()
分配大量内存并且不使用ffi.release()
分配大量内存并且不使用,PyPy (>= 5.7) 会更频繁地运行其GC以进行补偿,因此分配的总内存应保持在边界内无论如何; 但是显式调用ffi.release()
应该通过降低GC运行的频率来提高性能。
在 ffi.release(x)
之后,不要再使用 x
指向的任何内容。作为此规则的一个例外,您可以为完全相同的cdata对象x多次调用 ffi.release(x)
; 第一个之后的调用被忽略。
ffi.init_once()¶
ffi.init_once(function, tag): 运行 function()
一次。 tag
应该是标识函数的原始对象,如字符串: function()
仅在我们第一次看到 tag
时调用。 function()
的返回值将被当前和所有将来的 init_once()
用相同的标记记住并返回。 如果从多个线程并行调用 init_once()
则所有调用都会阻塞,直到执行 function()
为止。 如果
function()
引发异常,则会传播它,并且不会缓存任何内容 (即 如果我们捕获异常并再次尝试 init_once()
,将再次调用 function()
。). 版本1.4中的新功能。
例:
from _xyz_cffi import ffi, lib
def initlib():
lib.init_my_library()
def make_new_foo():
ffi.init_once(initlib, "init")
return lib.make_foo()
如果已经调用了 function()
,则 init_once()
被优化为非常快速地运行。 (在PyPy上,成本为零————JIT通常会删除它生成的机器代码中的所有内容。)
注意: init_once()
的一个 动机 是嵌入式案例中
"subinterpreters" 的CPython概念。 如果使用的是
out-of-line API 模式,即使存在多个子解释器,也只调用一次 function()
,并且所有子解释器之间共享其返回值。 目标是模仿传统的cpython C扩展模块的init代码总共只执行一次,即使有子解释器。在上面的示例中,C函数 init_my_library()
总共调用一次,而不是每个子解释器调用一次。 因此,避免
function()
中的python级副作用。 (因为它们只会应用于第一个子解释器中运行); 相反,返回一个值,如下例所示:
def init_get_max():
return lib.initialize_once_and_get_some_maximum_number()
def process(i):
if i > ffi.init_once(init_get_max, "max"):
raise IndexError("index too large!")
...
ffi.getctype(), ffi.list_types()¶
ffi.getctype("C type" or <ctype>, extra=""): 返回给定C类型的字符串表示形式。 如果非空,则追加"额外"字符串 (或插入更复杂的情况下的正确位置);它可以是要声明的变量的名称,也可以是类型的额外部分,如
like "*"
或 "[5]"
。 例如
ffi.getctype(ffi.typeof(x), "*")
返回C类型"指向与x相同类型的指针"的字符串表示形式; 并且
ffi.getctype("char[80]", "a") == "char a[80]"
。
ffi.list_types(): 返回此FFI实例已知的用户类型名称。这将返回一个包含三个名称列表的元组:
(typedef_names, names_of_structs, names_of_unions)
。 版本1.6中的新功能。
转换¶
本节介绍了在 写入 C数据结构(或将参数传递给函数调用)以及从C语言数据结构中 读取 (或获取函数调用的结果)时允许的所有转换。最后一列给出了允许的特定于类型的操作。
C语言类型 | 写入 | 读取 | 其他操作 |
---|---|---|---|
整形和枚举 [5] | 一个整数或int()返回的任何东西 (但不是浮点数!)。 必须在范围内。 | Python int或long,具体取决于类型 (版本1.10:或者bool) | int(), bool()
[6],
< |
char |
一个长度为1或类似<cdata char>的字符串 | 长度为1的字符串 | int(), bool(),
< |
wchar_t ,
char16_t ,
char32_t
[8] |
一个长度为1的unicode (如果是代理(码元),则可能是2个) 或其他类似的<cdata> | 一个长度为1的unicode (如果是代理(码元),则可能是2) | int(),
bool(), < |
float ,
double |
浮点数或float()返回的任何东西 | 一个Python浮点数 | float(), int(),
bool(), < |
long double |
类似带有 long double 的<cdata>,
或者float()返回的任何东西 |
一个<cdata>,以避免失去精度 [3] | float(), int(), bool() |
float
_Complex ,
double
_Complex |
一个复数或任何complex()返回的任何东西 | 一个Python复数 | complex(), bool() [7] |
指针 | 类似兼容类型的<cdata>
(即相同类型或 void* ,或作为数组 [1] |
一个<cdata> | [] [4],
+ , - ,
bool() |
void * |
类似带有任何指针或数组类型的<cdata> | ||
指向结构体 或 联合的指针 | 与指针相同 | [] , + ,
- , bool(),
和read/write struct 字段 |
|
函数指针 | 与指针相同 | bool(), call [2] | |
数组 | 列表或元组的元素 | 一个<cdata> | len(), iter(),
[] [4],
+ , - |
char[] ,
un/signed
char[] ,
_Bool[] |
与数组或Python字节字符串相同 | len(), iter(),
[] , + ,
- |
|
wchar_t[] ,
char16_t[] ,
char32_t[] |
与数组或Python unicode字符串相同 | len(), iter(),
[] ,
+ , - |
|
结构体 | 字段值的列表或元组或字典,或相同类型的<cdata> | 一个<cdata> | read/write 字段 |
联合 | 与struct相同,但最多只有一个字段 | read/write 字段 |
[1] item *
是函数参数中的 item[]
:
在函数声明中,根据C标准,
item *
参数与item[]
参数相同 (并且ffi.cdef()
不记录差异)。 所以当你调用这样一个函数时,你可以传递一个C类型接受的参数,例如将一个Python字符串传递给一个char *
参数 (因为它适用于char[]
参数) 或int *
参数的整数列表 (它适用于int[]
参数)。 请注意,即使您要传递单个item
,也需要在长度为1的列表中指定它; 例如,struct point_s *
参数可能会传递为[[x, y]]
或[{'x': 5, 'y': 10}]
。作为优化,CFFI假定具有
char *
参数的函数,您传递Python字符串将不会实际修改传入的字符数组,因此直接传递Python字符串对象内的指针。 (在PyPy上,这种优化仅在PyPy 5.4和CFFI 1.8之后才可用。)
[2] C函数调用在GIL释放时完成。
请注意,我们假设被调用的函数不使用Python.h中的Python API。例如,我们之后不会检查它们是否设置了Python异常。您可以解决它,但不建议将CFFI与Python.h
混合使用。 (如果您这样做,在PyPy和Windows等某些平台上,您可能需要显式链接到libpypy-c.dll
才能访问CPython C API兼容层;实际上,PyPy上的CFFI生成的模块本身并没有链接到libpypy-c.dll
。但实际上,首先不要这样做。)
[3] long double
的支持:
我们在cdata对象中保留long double
值以避免丢失精度。 普通Python浮点数只包含double
的精度。 如果你真的想将这样的对象转换为常规的Python float (即 Cdouble
), 请调用float()
。 如果你需要对这些数字进行算术而没有任何精度损失,你需要定义和使用一系列C函数,如long double add(long double a, long double b);
。
[4] 切片 x[start:stop]
:
只要你明确指定start
和stop
,就允许切片 (并且不给任何step
)。 它给出了一个"view"cdata对象,它是从start
到stop
的所有元素(数据项)。 它是数组类型的cdata (所以例如将它作为参数传递给C函数只会将其转换为指向start
元素的指针). 与索引一样,负边界意味着真正的负索引,如在C中。 至于切片赋值,它接受任何可迭代的,包括元素列表或另一个类似数组的cdata对象,但长度必须匹配。 (请注意,此行为与初始化不同: 例如 你可以这样chararray[10:15] = "hello"
,但是指定的字符串必须是正确的长度; 没有添加隐式空字符。)
[5] 枚举像int一样处理:
与C一样,枚举类型主要是int类型 (unsigned 或 signed, int 或 long; 请注意,GCC的首选是unsigned)。 例如,读取结构的枚举字段会返回一个整数。 要象征性地比较它们的值,请使用if x.field == lib.FOO
之类的代码。 如果你真的想要将它们的值作为字符串,请使用ffi.string(ffi.cast("the_enum_type", x.field))
。
[6] 原始cdata上的bool() :
版本1.7中的新功能。 在以前的版本中,它只适用于指针; 对于原语,它总是返回True。
N版本1.10中的新功能: C语言类型
_Bool
或bool
现在转换为Python布尔值。 如果 C_Bool
碰巧包含不同于0和1的值,则会出现异常 (这种情况在C中触发未定义的行为;如果你真的必须与依赖于它的库接口,不要在CFFI端使用_Bool
)。 此外,从字节字符串转换为_Bool[]
时,只接受字节\x00
和\x01
。
[7] libffi不支持复数:
版本1.11中的新功能: CFFI现在直接支持复数。 但请注意,libffi没有。 这意味着CFFI无法调用直接作为参数类型或返回complex类型的C函数,除非它们直接使用API模式。
[8] wchar_t
, char16_t
和 char32_t
请参阅下面的 Unicode字符类型。
支持文件¶
您可以使用 FILE *
参数声明C函数,并使用Python文件对象调用它们。 如果需要,你也可以这样做 c_f
= ffi.cast("FILE *", fileobj)
然后传递 c_f
。
但请注意,CFFI通过尽力而为的方法来做到这一点。 如果您需要更好地控制缓冲,刷新和及时关闭
FILE *
,那么您不应该对 FILE *
使用此特殊支持。
相反,您可以使用fdopen()处理您明确使用的常规 FILE *
cdata对象,如下所示:
ffi.cdef('''
FILE *fdopen(int, const char *); // from the C <stdio.h>
int fclose(FILE *);
''')
myfile.flush() # make sure the file is flushed
newfd = os.dup(myfile.fileno()) # make a copy of the file descriptor
fp = lib.fdopen(newfd, "w") # make a cdata 'FILE *' around newfd
lib.write_stuff_to_file(fp) # invoke the external function
lib.fclose(fp) # when you're done, close fp (and newfd)
无论如何,对 FILE *
的特殊支持在CPython 3.x和PyPy上以类似的方式实现,因为这些Python实现的文件本身不是基于 FILE *
。 这样做显式地提供了更多的控制。
Unicode字符类型¶
wchar_t
类型与底层平台具有相同的签名。 例如, 在Linux上,它是一个带符号的32位整数。
但是, char16_t
和 char32_t
类型 (版本1.11中的新功能)
始终是无符号的。
请注意,CFFI假定这些类型在本机字节序中包含UTF-16或UTF-32字符。更确切地说:
- 假设
char32_t
包含UTF-32或UCS4,它只是unicode码位; - 假设
char16_t
包含UTF-16,即UCS2加代理(码元); - 假设
wchar_t
包含UTF-32或UTF-16,基于其实际平台定义的大小为4或2个字节。
这一假设是真是假,C语言没有说明。
理论上,您正在链接的C语言库可以使用其中一种具有不同含义的类型。 然后,您需要自己处理它, 例如,在 cdef()
使用 uint32_t
而不是 char32_t
,并手动构建预期的 uint32_t
数组。
Python本身可以使用 sys.maxunicode == 65535
或
sys.maxunicode == 1114111
(Python >= 3.3 始终是 1114111)。 这改变了代理的处理方式 (这是一对16位"字符",实际上代表一个值大于65535的码位)。 如果您的Python是 sys.maxunicode == 1114111
,那么它可以存储任意unicode码位; 从Python unicodes转换为UTF-16时会自动插入代理,并在转换回时自动删除。 另一方面,如果你的Python是 sys.maxunicode == 65535
,那么它就是另一种方式: 从Python unicodes转换为UTF-32时会删除代理,并在转换回时添加。 换句话说,仅在存在大小不匹配时才进行代理转换。
请注意,未指定Python的内部表示。 例如, 在CPython >= 3.3, 它将使用1或2或4字节数组,具体取决于字符串实际包含的内容。 使用CFFI,当您将Python字节字符串传递给期望 char*
的C函数时,我们直接传递指向现有数据的指针,而无需临时缓冲区; 但是,由于内部表示的变化,使用unicode字符串参数和 wchar_t*
/ char16_t*
/
char32_t*
类型无法完成相同的操作。因此,为了保持一致性,CFFI总是为unicode字符串分配一个临时缓冲区。
警告: 现在,如果你将 char16_t
和 char32_t
与
set_source()
一起使用,你必须确保自己的类型是由你提供给 set_source()
的C语言源代码中声明的。 如果 #include
显式使用它们的库,则会声明它们,例如,使用C++ 11时。 否则,您需要在Linux上使用 #include
<uchar.h>
,或者通常使用类似于 typedef
uint16_t char16_t;
的方法。 这不是由CFFI自动完成的,因为
uchar.h
不是跨平台的标准,如果类型恰好已经定义,写上面的 typedef
会崩溃。