将FILE *传递给Python / ctypes中的函数
我有一个库函数(用C编写),它通过将输出写入FILE *
来生成文本。 我想在Python(2.7.x)中使用创建临时文件或管道的代码将其包装,将其传递给函数,从文件中读取结果,并将其作为Python字符串返回。
这是一个简单的例子来说明我的目标:
/* Library function */ void write_numbers(FILE * f, int arg1, int arg2) { fprintf(f, "%d %d\n", arg1, arg2); }
Python包装器:
from ctypes import * mylib = CDLL('mylib.so') def write_numbers( a, b ): rd, wr = os.pipe() write_fp = MAGIC_HERE(wr) mylib.write_numbers(write_fp, a, b) os.close(wr) read_file = os.fdopen(rd) res = read_file.read() read_file.close() return res #Should result in '1 2\n' being printed. print write_numbers(1,2)
我想知道我最好的选择是MAGIC_HERE()
。
我很想使用ctypes
并创建一个返回Python c_void_t的libc.fdopen()
包装器,然后将其传递给库函数。 我觉得这在理论上应该是安全的 – 只是想知道这种方法是否存在问题,或者现有的Python主题是否能解决这个问题。
此外,这将进入一个长期运行的过程(让我们假设“永远”),所以任何泄露的文件描述符将是有问题的。
首先,请注意FILE*
是特定于stdio的实体。 它在系统级别不存在。 系统级中存在的东西是UNIX中的描述符(使用file.fileno()
检索)( os.pipe()
已经返回普通描述符)并在Windows中处理(使用msvcrt.get_osfhandle()
检索)。 因此,如果可以有多个C运行时,那么它作为库间交换格式是一个糟糕的选择。 如果您的库是针对另一个C运行库而不是您的Python副本编译的,那么您将遇到麻烦:1)结构的二进制布局可能不同(例如,由于对齐或用于调试目的的其他成员或甚至不同的类型大小); 2)在Windows中,结构链接到的文件描述符也是特定于C的实体,它们的表由内部的C运行时维护1 。
此外,在Python 3中,对I / O进行了大修,以便从stdio
解开它。 因此, FILE*
与Python风格不同(可能也是大多数非C风味)。
现在,你需要的是
- 以某种方式猜测你需要哪个C运行时,以及
- 调用它的
fdopen()
(或等价物)。
(毕竟,Python的一个座右铭是 “让正确的事情变得容易而且错误的事情”
最干净的方法是使用库链接到的精确实例(请祈祷它与动态链接或不会导出符号)
对于第一项,我找不到任何可以分析加载的动态模块的元数据的Python模块,以找出它已链接到的哪些DLL(只是名称甚至名称+版本是不够的,你知道,由于系统上可能有多个库实例)。 虽然它的格式信息可以广泛使用,但它绝对是可能的。
对于第二项,它是一个简单的ctypes.cdll('path').fdopen
( _fdopen
for MSVCRT)。
其次,您可以执行一个小帮助程序模块,该模块将针对与库相同(或保证兼容)的运行时进行编译,并将为您执行上述描述符/句柄的转换。 这实际上是正确编辑库的一种解决方法。
最后,通过ctypes.pythonapi
提供的Python C API,使用Python的C运行时实例(所有上述警告全部适用)是最简单(也是最脏的)方法。 它利用了
- 事实上,Python 2的类文件对象是
stdio
的FILE*
包装器(Python 3不是) -
PyFile_AsFile
API返回包装的FILE*
(注意它在Python 3中缺失 )- 对于独立的
fd
,你需要首先构造一个类文件对象(这样就会有一个FILE*
返回;))
- 对于独立的
-
事物的
id()
是它的内存地址(CPython特定的) 2>>> open("test.txt")
>>> f=_ >>> f.fileno() 3 >>> ctypes.pythonapi >>> api=_ >>> api.PyFile_AsFile <_funcptr object at 0x018557B0> >>> api.PyFile_AsFile.restype=ctypes.c_void_p #as per ctypes docs, # pythonapi assumes all fns # to return int by default >>> api.PyFile_AsFile.argtypes=(ctypes.c_void_p,) # as of 2.7.10, long integers are #silently truncated to ints, see http://bugs.python.org/issue24747 >>> api.PyFile_AsFile(id(f)) 2019259400
请记住,使用fd
和C指针,您需要手动确保正确的对象生命周期!
-
os.fdopen()
返回的类文件对象会关闭.close()
上的描述符- 如果在关闭/垃圾收集文件对象后需要它们,则使用
os.dup()
重复描述符
- 如果在关闭/垃圾收集文件对象后需要它们,则使用
- 在使用C结构时,使用
PyFile_IncUseCount()
/PyFile_DecUseCount()
调整相应对象的引用计数。 - 确保描述符/文件对象上没有其他I / O,因为它会搞砸数据(例如,自从
for l in f
调用iter(f)
/for l in f
,内部缓存完全独立于stdio
的缓存)