Передать файл * в функцию из 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_AsFileAPI, который возвращает обернутый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с кэшированием)