First, do note that FILE*
is an stdio-specific entity. It doesn't exist at system level. The things that exist at system level are descriptors (retrieved with file.fileno() https://docs.python.org/2/library/stdtypes.html?highlight=fileno#file.fileno) in UNIX (os.pipe()
returns plain descriptors already) and handles (retrieved with msvcrt.get_osfhandle() https://docs.python.org/2/library/msvcrt.html?highlight=msvcrt#msvcrt.get_osfhandle) in Windows. Thus it's a poor choice as an inter-library exchange format if there can be more than one C runtime in action. You'll be in trouble if your library is compiled against another C runtime than your copy of Python: 1) binary layouts of the structure may differ (e.g. due to alignment or additional members for debugging purposes or even different type sizes); 2) in Windows, file descriptors that the structure links to are C-specific entities as well, and their table is maintained by a C runtime internally1 https://stackoverflow.com/questions/33310675/pass-file-into-function-from-python-ctypes/33311066#comment54499370_33311066.
此外,在 Python 3 中,对 I/O 进行了彻底修改,以便将其从stdio
. So, FILE*
与 Python 风格格格不入(很可能,大多数非 C 风格也是如此)。
现在,你需要的是
- 以某种方式猜测您需要哪个 C 运行时,并且
- 称其为fdopen() http://linux.die.net/man/3/fdopen(或同等学历)。
(Python 的座右铭之一is毕竟,“让正确的事情变得容易,让错误的事情变得困难”)
最干净的方法是使用库链接到的精确实例(请祈祷它是动态链接的,否则将没有导出的符号可供调用)
对于第一项,我找不到任何可以分析加载的动态模块元数据以找出它已链接的 DLL/so 的 Python 模块(仅名称甚至名称+版本是不够的,你知道,由于系统上可能存在该库的多个实例)。尽管这绝对是可能的,因为有关其格式的信息是广泛可用的。
对于第二项,这是一个微不足道的事情ctypes.cdll('path').fdopen
(_fdopen
对于 MSVCRT)。
其次,您可以创建一个小型帮助程序模块,该模块将针对与库相同(或保证兼容)的运行时进行编译,并为您从上述描述符/句柄进行转换。这实际上是编辑库的有效解决方法。
最后,有一个最简单(也是最脏)的方法,通过 Python C API 使用 Python 的 C 运行时实例(因此上述所有警告都完全适用)ctypes.pythonapi https://docs.python.org/2/library/ctypes.html?highlight=pythonapi。它利用了
- Python 2 的类文件对象是包装器这一事实
stdio
's FILE*
(Python 3 则不然)
-
PyFile_AsFile https://docs.python.org/2/c-api/file.html?highlight=pyfile_asfile#c.PyFile_AsFile API that returns the wrapped
FILE*
(note that it's missing from Python 3 https://docs.python.org/3/c-api/file.html?highlight=pyfile_asfile#c.PyFile_AsFile)
- 对于独立的
fd
,你需要首先构造一个类似文件的对象(这样就会有一个FILE*
回来 ;) )
-
the fact that id() https://docs.python.org/2/library/functions.html?highlight=id#id of an object is its memory address (CPython-specific)2 https://stackoverflow.com/questions/33310675/pass-file-into-function-from-python-ctypes/33311066#comment54426705_33311066
>>> 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
请记住fd
s 和 C 指针,您需要手动确保正确的对象生命周期!
- file-like objects returned by
os.fdopen()
do close the descriptor on .close()
- 所以重复描述符
os.dup()
如果您在文件对象关闭/垃圾收集后需要它们
- 在使用 C 结构时,调整相应对象的引用计数PyFile_IncUseCount() https://docs.python.org/2/c-api/file.html?highlight=pyfile_asfile#c.PyFile_IncUseCount/PyFile_DecUseCount() https://docs.python.org/2/c-api/file.html?highlight=pyfile_asfile#c.PyFile_DecUseCount.
- 确保描述符/文件对象上没有其他 I/O,因为它会搞砸数据(例如,自从调用
iter(f)
/for l in f
,内部缓存是独立于stdio
的缓存)