Pass FILE * into function from Python / ctypes

Brian McFarland

I have a library function (written in C) that generates text by writing the output to FILE *. I want to wrap this in Python (2.7.x) with code that creates a temp file or pipe, passes it into the function, reads the result from the file, and returns it as a Python string.

Here's a simplified example to illustrate what I'm after:

/* Library function */
void write_numbers(FILE * f, int arg1, int arg2)
{
   fprintf(f, "%d %d\n", arg1, arg2);
}

Python wrapper:

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)

I'm wondering what my best bet is for MAGIC_HERE().

I'm tempted to just use ctypes and create a libc.fdopen() wrapper that returns a Python c_void_t, then pass that into the library function. I'm seems like that should be safe in theory--just wondering if there are issues with that approach or an existing Python-ism to solve this problem.

Also, this will go in a long-running process (lets just assume "forever"), so any leaked file descriptors are going to be problematic.

ivan_pozdeev

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()) in UNIX (os.pipe() returns plain descriptors already) and handles (retrieved with 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.

Moreover, in Python 3, I/O was overhauled in order to untangle it from stdio. So, FILE* is alien to that Python flavor (and likely, most non-C flavors, too).

Now, what you need is to

  • somehow guess which C runtime you need, and
  • call its fdopen() (or equivalent).

(One of Python's mottoes is "make the right thing easy and the wrong thing hard", after all)


The cleanest method is to use the precise instance that the library is linked to (do pray that it's linked with it dynamically or there'll be no exported symbol to call)

For the 1st item, I couldn't find any Python modules that can analyze loaded dynamic modules' metadata to find out which DLLs/so's it have been linked with (just a name or even name+version isn't enough, you know, due to possible multiple instances of the library on the system). Though it's definitely possible since the information about its format is widely available.

For the 2nd item, it's a trivial ctypes.cdll('path').fdopen (_fdopen for MSVCRT).


Second, you can do a small helper module that would be compiled against the same (or guaranteed compatible) runtime as the library and would do the conversion from the aforementioned descriptor/handle for you. This is effectively a workaround to editing the library proper.


Finally, there's the simplest (and the dirtiest) method using Python's C runtime instance (so all the above warnings apply in full) through Python C API available via ctypes.pythonapi. It takes advantage of

  • the fact that Python 2's file-like objects are wrappers over stdio's FILE* (Python 3's are not)
  • PyFile_AsFile API that returns the wrapped FILE* (note that it's missing from Python 3)
    • for a standalone fd, you need to construct a file-like object first (so that there would be a FILE* to return ;) )
  • the fact that id() of an object is its memory address (CPython-specific)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
    

Do keep in mind that with fds and C pointers, you need to ensure proper object lifetimes by hand!

  • file-like objects returned by os.fdopen() do close the descriptor on .close()
    • so duplicate descriptors with os.dup() if you need them after a file object is closed/garbage collected
  • while working with the C structure, adjust the corresponding object's reference count with PyFile_IncUseCount()/PyFile_DecUseCount().
  • ensure no other I/O on the descriptors/file objects since it would screw up the data (e.g. ever since calling iter(f)/for l in f, internal caching is done that's independent from stdio's caching)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How to pass a struct from python to a ctypes function in a shared object?

From Dev

How to pass lists into a ctypes function on python

From Dev

Calling CPP function from python ctypes

From Dev

How to pass a python list to C function (dll) using ctypes

From Dev

using python ctypes pass a long vector by reference to a c++ function

From Dev

How to pass python 2d matrix to C function ctypes

From Dev

Python Function: using a batch file to pass parameters from .txt file to python function and execute function

From Dev

Returning a C array from a C function to Python using ctypes

From Dev

Callbacks with ctypes (How to call a python function from C)

From Dev

Calling a Go string function from Python ctypes results in segfault

From Dev

ctypes - get output from c++ function into python object

From Dev

C function called from Python via ctypes returns incorrect value

From Dev

Pointer from Python (ctypes) to C to save function output

From Dev

Have to pass input from first python file to second python file and second python file function should be called inside first python file

From Dev

ctypes from buffer - Python

From Dev

How to pass (rapidly updated) variable from ctypes C code to Python? Pass by reference?

From Dev

Ctypes pass float into function returns random numbers

From Dev

Python 3: How to call function from another file and pass arguments to that function ?

From Dev

ctypes convert Python boolean array to C++ boolean array to pass into function

From Dev

Using Python and ctypes to pass a variable length string inside a structure to c function (generated by Matlab Coder)

From Dev

Pass Python function from outside to another function

From Dev

How to automatically pass an input value to python "input" function, when running python script from batch file

From Dev

Pass FILE* pointer from Swift to C Function

From Dev

How to pass a text file as an argument to a function in python

From Dev

How to pass (non-empty) list of lists from Python to C++ via ctypes?

From Dev

Python ctypes: pass argument by reference error

From Dev

Pass file descriptor via a dbus function call from Python (aka call flatpak's HostCommand)

From Dev

Pass/indent each line from .txt file as parameter value in python function

From Dev

C++ function in python from .dll using ctypes - function not found and access violation

Related Related

  1. 1

    How to pass a struct from python to a ctypes function in a shared object?

  2. 2

    How to pass lists into a ctypes function on python

  3. 3

    Calling CPP function from python ctypes

  4. 4

    How to pass a python list to C function (dll) using ctypes

  5. 5

    using python ctypes pass a long vector by reference to a c++ function

  6. 6

    How to pass python 2d matrix to C function ctypes

  7. 7

    Python Function: using a batch file to pass parameters from .txt file to python function and execute function

  8. 8

    Returning a C array from a C function to Python using ctypes

  9. 9

    Callbacks with ctypes (How to call a python function from C)

  10. 10

    Calling a Go string function from Python ctypes results in segfault

  11. 11

    ctypes - get output from c++ function into python object

  12. 12

    C function called from Python via ctypes returns incorrect value

  13. 13

    Pointer from Python (ctypes) to C to save function output

  14. 14

    Have to pass input from first python file to second python file and second python file function should be called inside first python file

  15. 15

    ctypes from buffer - Python

  16. 16

    How to pass (rapidly updated) variable from ctypes C code to Python? Pass by reference?

  17. 17

    Ctypes pass float into function returns random numbers

  18. 18

    Python 3: How to call function from another file and pass arguments to that function ?

  19. 19

    ctypes convert Python boolean array to C++ boolean array to pass into function

  20. 20

    Using Python and ctypes to pass a variable length string inside a structure to c function (generated by Matlab Coder)

  21. 21

    Pass Python function from outside to another function

  22. 22

    How to automatically pass an input value to python "input" function, when running python script from batch file

  23. 23

    Pass FILE* pointer from Swift to C Function

  24. 24

    How to pass a text file as an argument to a function in python

  25. 25

    How to pass (non-empty) list of lists from Python to C++ via ctypes?

  26. 26

    Python ctypes: pass argument by reference error

  27. 27

    Pass file descriptor via a dbus function call from Python (aka call flatpak's HostCommand)

  28. 28

    Pass/indent each line from .txt file as parameter value in python function

  29. 29

    C++ function in python from .dll using ctypes - function not found and access violation

HotTag

Archive