#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()
.
Пара вопросов:
- Это самый удобный способ? Или есть встроенный метод в API Python C, который я не видел?
- В
fileno()
документации упоминается: «Верните базовый файловый дескриптор (целое число) потока, если он существует. Возникает ошибка ОС, если объект ввода-вывода не использует дескриптор файла «. В каком случае это произойдет? Что, если я передам дескриптор файла, созданный в Python, другим, чемopen()
? - Кажется довольно безопасным открывать дескриптор 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 )
.