Передать файл * в функцию из Python / ctypes
у меня есть библиотечная функция (написанная на C), которая генерирует текст, записывая вывод в FILE *
. Я хочу обернуть это в Python (2.7.x) с кодом, который создает временный файл или канал, передает его в функцию, считывает результат из файла и возвращает его как строку Python.
вот упрощенный пример, чтобы проиллюстрировать, что мне нужно:
/* Library function */
void write_numbers(FILE * f, int arg1, int arg2)
{
fprintf(f, "%d %dn", 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 2n' being printed.
print write_numbers(1,2)
мне интересно, что мой лучший выбор для MAGIC_HERE()
.
у меня есть соблазн просто использовать ctypes
и создать libc.fdopen()
оболочка, которая возвращает Python c_void_t, а затем передает это в функцию библиотеки. Мне кажется, что это должно быть безопасно в теории-просто интересно, есть ли проблемы с этим подходом или существующим Python-ism для решения этой проблемы.
кроме того, это будет длительным процессом (давайте просто предположим "навсегда"), поэтому любые просочившиеся файловые дескрипторы будут проблематичными.
1 ответов
во-первых, обратите внимание, что FILE*
- это объект, специфичный для stdio. Он не существует на системном уровне. Вещи, которые существуют на системном уровне, являются дескрипторами (полученными с помощью file.fileno()
) в UNIX (os.pipe()
уже возвращает простые дескрипторы) и дескрипторы (извлекаются с помощью msvcrt.get_osfhandle()
) в Windows. таким образом, это плохой выбор в качестве формата обмена между библиотеками, если в действии может быть более одной среды выполнения C. у вас будут проблемы, если ваша библиотека скомпилирован против другой среды выполнения C, чем ваша копия Python: 1) бинарные макеты структуры могут отличаться (например, из-за выравнивания или дополнительных членов для целей отладки или даже разных размеров типов); 2) в Windows, файловые дескрипторы, к которым ссылается структура, также являются специфичными для C сущностями, и их таблица поддерживается средой выполнения C внутренне1.
более того, в Python 3 ввод-вывод был переработан, чтобы распутать его из stdio
. Так, FILE*
чужд этому аромату Python (и, вероятно, большинство ароматов, отличных от C).
теперь, что вам нужно, это
- как-то угадайте, какая среда выполнения C вам нужна, и
- называем его
fdopen()
(или эквивалент).
(один из девизов Python is "сделать правильную вещь легкой, а неправильную вещь трудной", в конце концов)
самый чистый метод-использовать точный экземпляр, который библиотека связана с (молитесь, чтобы она была связана с ней динамически, или не будет экспортированного символа для вызова)
для 1-го элемента я не смог найти модули Python, которые могут анализировать метаданные загруженных динамических модулей, чтобы узнать, с какими DLL/so он был связан (просто имя или даже имя+версия недостаточно, вы знаете, из-за возможных нескольких экземпляров библиотеки в системе). Хотя это определенно возможно, так как информация о его формате широко доступный.
для 2-го пункта это тривиально ctypes.cdll('path').fdopen
(_fdopen
для MSVCRT).
во-вторых, вы можете сделать небольшой вспомогательный модуль, который будет скомпилирован с той же (или гарантированной совместимой) средой выполнения, что и библиотека, и сделает преобразование из вышеупомянутого дескриптора/дескриптора для вас. Это фактически обходной путь для редактирования библиотеки.
наконец, есть самый простой (и самый грязный) метод с использованием Python C экземпляр среды выполнения (поэтому все вышеуказанные предупреждения применяются полностью) через Python C API, доступный через ctypes.pythonapi
. Он использует
- тот факт, что файловые объекты Python 2 являются обертками над
stdio
' sFILE*
(Python 3 не являются) -
PyFile_AsFile
API, который возвращает обернутыйFILE*
(заметим, что он отсутствует в Python 3)- для автономной
fd
, вам нужно построить сначала объект, подобный файлу (так что будетFILE*
вернуться ;) )
- для автономной
-
тот факт, что
id()
объекта является его адрес памяти (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
имейте в виду, что с fd
указатели s и C, вам нужно обеспечить правильное время жизни объекта вручную!
- файлоподобные объекты, возвращаемые
os.fdopen()
do закрыть дескриптор.close()
- поэтому дублируйте дескрипторы с
os.dup()
если они вам нужны после того, как файловый объект закрыт / собран мусор
- поэтому дублируйте дескрипторы с
- при работе со структурой C настройте счетчик ссылок соответствующего объекта с помощью
PyFile_IncUseCount()
/PyFile_DecUseCount()
. - не гарантируйте никакого другого ввода-вывода на дескрипторах / файловых объектах, так как это испортит данные (например, с момента вызова
iter(f)
/for l in f
, внутреннее кэширование выполняется независимо отstdio
с кэшированием)