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

时间:2015-10-23 20:13:41

标签: python c ctypes

我有一个库函数(用C编写),它通过将输出写入/* Library function */ void write_numbers(FILE * f, int arg1, int arg2) { fprintf(f, "%d %d\n", arg1, arg2); } 来生成文本。我想在Python(2.7.x)中使用创建临时文件或管道的代码将其包装,将其传递给函数,从文件中读取结果,并将其作为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)

Python包装器:

MAGIC_HERE()

我想知道ctypes最好的选择是什么。

我很想使用libc.fdopen()并创建一个返回Python c_void_t的 editForm.$rollbackViewValue() 包装器,然后将其传递给库函数。我觉得这在理论上应该是安全的 - 只是想知道这种方法是否存在问题,或者现有的Python主题是否存在问题。

此外,这将进入一个长期运行的过程(让我们假设"永远"),因此任何泄露的文件描述符都会有问题。

1 个答案:

答案 0 :(得分:4)

首先,请注意FILE*是特定于stdio的实体。它不存在于系统级别。系统级中存在的东西是UNIX中的描述符(使用file.fileno()检索)(os.pipe()已经返回普通描述符)和处理(使用msvcrt.get_osfhandle()检索)在Windows中。 因此,如果可以有多个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(MSVCRT的_fdopen)。

其次,您可以执行一个小帮助程序模块,该模块将针对与库相同(或保证兼容)的运行时进行编译,并将为您执行上述描述符/句柄的转换。这实际上是正确编辑库的一种解决方法。

最后,通过ctypes.pythonapi提供的Python C API,使用Python的C运行时实例(所有上述警告全部适用)是最简单(也是最脏)的方法。它利用了

  • 事实上,Python 2的文件类对象是stdio FILE*的包装器(Python 3&#39}不是)
  • 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的缓存)