gpt4 book ai didi

python - 将 FILE * 传递给 Python/ctypes 中的函数

转载 作者:太空狗 更新时间:2023-10-29 19:26:36 24 4
gpt4 key购买 nike

我有一个库函数(用 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/so 链接(仅名称甚至名称+版本是不够的,你知道,由于系统上可能有多个库实例)。尽管这绝对是可能的,因为有关其格式的信息已广泛可用。

对于第二项,它是一个普通的 ctypes.cdll('path').fdopen(_fdopen 用于 MSVCRT)。


其次,你可以做一个小的辅助模块,它会针对与库相同(或保证兼容)的运行时进行编译,并会为你从上述描述符/句柄进行转换。这是正确编辑库的有效解决方法。


最后,有一个使用 Python 的 C 运行时实例的最简单(也是最肮脏的)方法(因此上述所有警告完全适用)通过 Python C API 可通过 ctypes.pythonapi 获得。 .它利用了

  • 事实是 Python 2 的类文件对象是 stdioFILE* 的包装器(Python 3 不是)
  • PyFile_AsFile返回包装的 FILE* 的 API(注意 it's missing from Python 3 )
    • 对于独立的fd,您需要先构造一个类似文件的对象(以便返回一个FILE* ;))
  • 事实 id()一个对象的是它的内存地址(特定于 CPython) 2

    >>> 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 和 C 指针,您需要手动确保正确的对象生命周期!

  • os.fdopen() 返回的类文件对象确实在 .close() 上关闭了描述符
    • 如果在文件对象关闭/垃圾收集后需要它们,请使用 os.dup() 复制描述符
  • 在使用 C 结构时,使用 PyFile_IncUseCount() 调整相应对象的引用计数。/PyFile_DecUseCount() .
  • 确保描述符/文件对象上没有其他 I/O,因为它会搞砸数据(例如自从调用 iter(f)/for l in f , 内部缓存独立于 stdio 的缓存)

关于python - 将 FILE * 传递给 Python/ctypes 中的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33310675/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com