我有一个库函数(用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);
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
我想知道我最好的选择是什么MAGIC_HERE().
我很想使用ctypes并创建一个libc.fdopen()返回Python c_void_t 的包装器,然后将其传递给库函数.我觉得理论上应该是安全的 - 只是想知道这种方法是否存在问题,或者现有的Python主题是否存在解决这个问题的问题.
此外,这将进入一个长期运行的过程(让我们假设"永远"),所以任何泄露的文件描述符将是有问题的.
首先,请注意这FILE*是一个特定于 stdio 的实体。它不存在于系统级别。系统级别存在的东西是file.fileno()UNIX 中的描述符(使用 检索)(已返回普通描述符)和Windows 中的os.pipe()句柄(使用 检索)。因此,如果有多个 C 运行时在运行,那么作为库间交换格式,它是一个糟糕的选择。如果您的库是针对另一个 C 运行时而不是 Python 副本进行编译的,您将会遇到麻烦:1)结构的二进制布局可能不同(例如,由于对齐或用于调试目的的附加成员,甚至不同的类型大小);2) 在 Windows 中,该结构链接到的文件描述符也是 C 特定的实体,并且它们的表由 C 运行时内部维护1。msvcrt.get_osfhandle()
此外,在 Python 3 中,对 I/O 进行了彻底修改,以便将其从stdio. 因此,FILE*它与 Python 风格格格不入(很可能也与大多数非 C 风格格格不入)。
现在,你需要的是
fdopen()(或等效的)。(毕竟,Python 的座右铭之一是“让正确的事情变得简单,让错误的事情变得困难”)
最干净的方法是使用库链接到的精确实例(请祈祷它是动态链接的,否则将没有导出的符号可供调用)
对于第一项,我找不到任何可以分析加载的动态模块元数据以找出它已链接的 DLL/so 的 Python 模块(仅名称甚至名称+版本是不够的,你知道,由于系统上可能存在该库的多个实例)。尽管这绝对是可能的,因为有关其格式的信息是广泛可用的。
对于第二项,这是一个微不足道的事情ctypes.cdll('path').fdopen(_fdopen对于 MSVCRT)。
其次,您可以创建一个小型帮助程序模块,该模块将针对与库相同(或保证兼容)的运行时进行编译,并为您从上述描述符/句柄进行转换。这实际上是编辑库的有效解决方法。
最后,有一个最简单(也是最肮脏)的方法,通过通过ctypes.pythonapi. 它利用了
stdio(FILE*Python 3 不是)PyFile_AsFile返回包装的 API FILE*(请注意,Python 3 中缺少它)
fd,您需要首先构造一个类似文件的对象(这样就会有一个FILE*返回;))>>> open("test.txt")
<open file 'test.txt', mode 'r' at 0x017F8F40>
>>> f=_
>>> f.fileno()
3
>>> ctypes.pythonapi
<PyDLL 'python dll', handle 1e000000 at 12808b0>
>>> 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
Run Code Online (Sandbox Code Playgroud)请记住,对于fds 和 C 指针,您需要手动确保正确的对象生命周期!
os.fdopen()do 关闭描述符返回的类文件对象.close()
os.dup()如果在文件对象关闭/垃圾收集后需要它们,请重复描述符PyFile_IncUseCount()/调整相应对象的引用计数PyFile_DecUseCount()。iter(f)/后for l in f,内部缓存就独立于 的stdio缓存)