Поток C `FILE` из объекта bufferedIO Python

#python #c #ctypes #file-descriptor

#python #c #ctypes #файловый дескриптор

Вопрос:

Я пишу привязку Python для функции библиотеки C, для которой требуется FILE * дескриптор в качестве входных данных.

Я хочу, чтобы вызывающий Python передавал открытый io.BufferedReader объект функции, чтобы сохранить контроль над дескриптором, например:

 with open(fname, 'rb') as fh:
    my_c_function(fh)
 

Поэтому я не хочу передавать имя файла и открывать дескриптор внутри функции C.

Моя оболочка C будет выглядеть примерно так:

 PyObject *my_c_function (PyObject *self, PyObject *args)
{
    FILE *fh;
    if (! PyArgs_ParseTuple (args, "?", amp;fh)) return NULL;
    my_c_lib_function (fh);
    // [...]
}
 

Очевидно, я не могу понять, для какого символа я должен использовать "?" , или мне следует использовать другой метод, чем PyArgs_ParseTuple .
Документация Python C API, похоже, не содержит никаких примеров того, как обращаться с буферизованными объектами ввода-вывода (насколько я понимаю, протокол Buffer применяется к объектам bytes и co …. правильно?)

Похоже, я мог бы заглянуть в дескриптор файла объекта дескриптора Python в моей оболочке C (как при вызове fileno() ) и создать дескриптор файла C из этого использования fdopen() .

Пара вопросов:

  1. Это самый удобный способ? Или есть встроенный метод в API Python C, который я не видел?
  2. В fileno() документации упоминается: «Верните базовый файловый дескриптор (целое число) потока, если он существует. Возникает ошибка ОС, если объект ввода-вывода не использует дескриптор файла «. В каком случае это произойдет? Что, если я передам дескриптор файла, созданный в Python, другим, чем open() ?
  3. Кажется довольно безопасным открывать дескриптор C, доступный только для чтения, в fd, доступном только для чтения, открытом Python, который должен гарантированно закрывать дескриптор после функции C; однако, может ли кто-нибудь подумать о каких-либо подводных камнях в этом подходе?

Комментарии:

1. @101 Я не думаю, что здесь применим тег ctypes, так как я использую собственный C API.

Ответ №1:

Не уверен, что это наиболее разумный способ, но я решил его в Linux следующим образом:

 static PyObject *
get_fh_from_python_fh (PyObject *self, PyObject *args)
{
    PyObject *buf, *fileno_fn, *fileno_obj, *fileno_args;
    if (! PyArg_ParseTuple (args, "O", amp;buf)) return NULL;

    // Get the file descriptor from the Python BufferedIO object.
    // FIXME This is not sure to be reliable. See
    // https://docs.python.org/3/library/io.html#io.IOBase.fileno
    if (! (fileno_fn = PyObject_GetAttrString (buf, "fileno"))) {
        PyErr_SetString (PyExc_TypeError, "Object has no fileno function.");
        return NULL;
    }
    fileno_args = PyTuple_New(0);
    if (! (fileno_obj = PyObject_CallObject (fileno_fn, fileno_args))) {
        PyErr_SetString (PyExc_SystemError, "Error calling fileno function.");
        return NULL;
    }
    int fd = PyLong_AsSize_t (fileno_obj);

    /*
     * From the Linux man page:
     *
     * > The file descriptor is not dup'ed, and will be closed when the stream
     * > created by fdopen() is closed. The result of applying fdopen() to a
     * > shared memory object is undefined.
     *
     * This handle must not be closed. Leave open for the Python caller to
     * handle it.
     */
    FILE *fh = fdopen (fd, "r");

    // rest of the code...
}
 

Это имеет в виду только Linux, но пока он делает то, что ему нужно. Лучшим подходом было бы получить представление об объекте BufferedReader и, возможно, даже найти FILE * там; но если это не является частью API Python, это может привести к сбоям в будущих версиях.

Комментарии:

1. Этот дескриптор не должен быть закрыт , что означает FILE * утечку структуры и всего, что в ней есть. Вы можете добавить fd = dup( fd ); строку перед вызовом fdopen() , а затем вы можете безопасно закрыть fh fclose( fh ) .