Как вы вызываете код Python из кода C?
-
20-08-2019 - |
Вопрос
Я хочу расширить большой проект на C некоторыми новыми функциональными возможностями, но я действительно хочу написать его на Python.По сути, я хочу вызвать Python-код из C-кода.Однако оболочки Python-> C, такие как SWIG, допускают ОБРАТНОЕ, то есть пишут модули C и вызывают C из Python.
Я рассматриваю подход, включающий IPC или RPC (я не возражаю против использования нескольких процессов);то есть мой компонент на чистом Python запускается в отдельном процессе (на том же компьютере) и мой проект C взаимодействует с ним путем записи / чтения из сокета (или канала unix).мой компонент python может читать / записывать в сокет для связи.Это разумный подход?Есть ли что-нибудь получше?Как какой-то специальный механизм RPC?
Спасибо за ответ на данный момент - однако я хотел бы сосредоточиться на подходах, основанных на IPC, поскольку я хочу, чтобы моя программа на Python была в отдельном процессе, как моя программа на C.Я не хочу встраивать интерпретатор Python.Спасибо!
Решение
Я рекомендую подходы, подробно описанные здесь.Он начинается с объяснения того, как выполнять строки кода Python, затем оттуда подробно описывается, как настроить среду Python для взаимодействия с вашей программой на C, вызывать функции Python из вашего кода на C, манипулировать объектами Python из вашего кода на C и т.д.
Редактировать:Если вы действительно хотите пойти по пути IPC, то вам захочется использовать модуль struct ( структура) или еще лучше, протолиб.Большая часть взаимодействия между процессами Python и C вращается вокруг передачи структур туда и обратно, либо через розетку или через общая память.
Я рекомендую создать Command
структура с полями и кодами для представления команд и их аргументов.Я не могу дать более конкретный совет, не зная больше о том, чего вы хотите достичь, но в целом я рекомендую протолиб библиотека, поскольку это то, что я использую для связи между программами на C и Python (отказ от ответственности:Я являюсь автором protlib).
Другие советы
Смотрите соответствующую главу в руководстве: http://docs.python.org/extending/
По сути, вам придется встроить интерпретатор python в вашу программу.
Рассматривали ли вы возможность просто обернуть ваше приложение python в сценарий оболочки и вызвать его из with в вашем приложении C?
Не самое элегантное решение, но оно очень простое.
Я не использовал подход IPC для Python<-> C коммуникация, но она должна работать довольно хорошо.Я бы попросил программу C выполнить стандартный fork-exec и использовать перенаправленный stdin
и stdout
в дочернем процессе для связи.Приятное текстовое общение очень упростит разработку и тестирование программы на Python.
Если бы я решил пойти с IPC, я бы, вероятно, разорился на XML-RPC -- кроссплатформенность, позволяет вам легко перенести серверный проект Python на другой узел позже, если вы захотите, имеет много отличных реализаций (см. здесь для многих, включая C и Python, и здесь для простого XML-RPC-сервера, который является частью стандартной библиотеки Python - не такой масштабируемый, как другие подходы, но, вероятно, прекрасный и удобный для вашего варианта использования).
Возможно, это не идеальный IPC-подход для всех случаев (или даже не идеальный RPC -подход, во что бы то ни стало!), Но, на мой взгляд, удобство, гибкость, надежность и широкий спектр реализаций перевешивают множество мелких недостатков.
по-видимому, Python должен иметь возможность компилироваться в win32 dll, это решит проблему
Таким образом, преобразование кода c # в библиотеки DLL win32 сделает его пригодным для использования любым инструментом разработки
Это кажется довольно милым http://thrift.apache.org/, есть даже книга об этом.
Подробные сведения:
Программная платформа Apache Thrift для масштабируемой межъязыковой разработки сервисов сочетает программный стек с генерацией кода движок для создания сервисов, которые эффективно и без проблем работают между C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C #, Cocoa, JavaScript, Node.js, Smalltalk, OCaml и Delphi и другие языки.
Я использовал "стандартный" подход Встраивание Python в другое приложение.Но это сложно / утомительно.Каждая новая функция в Python болезненна для реализации.
Я видел пример Вызов PyPy из C.Он использует CFFI для упрощения интерфейса, но для этого требуется PyPy, а не Python.Сначала прочтите и поймите этот пример, по крайней мере, на высоком уровне.
Я изменил пример C / PyPy для работы с Python.Вот как вызвать Python из C с помощью CFFI.
Мой пример более сложный, потому что я реализовал три функции в Python вместо одной.Я хотел бы осветить дополнительные аспекты передачи данных туда и обратно.
Сложная часть теперь изолирована от передачи адреса api
к Python.Это должно быть реализовано только один раз.После этого легко добавлять новые функции в Python.
интерфейс.h
// These are the three functions that I implemented in Python.
// Any additional function would be added here.
struct API {
double (*add_numbers)(double x, double y);
char* (*dump_buffer)(char *buffer, int buffer_size);
int (*release_object)(char *obj);
};
тест_сffi.c
//
// Calling Python from C.
// Based on Calling PyPy from C:
// http://doc.pypy.org/en/latest/embedding.html#more-complete-example
//
#include <stdio.h>
#include <assert.h>
#include "Python.h"
#include "interface.h"
struct API api; /* global var */
int main(int argc, char *argv[])
{
int rc;
// Start Python interpreter and initialize "api" in interface.py using
// old style "Embedding Python in Another Application":
// https://docs.python.org/2/extending/embedding.html#embedding-python-in-another-application
PyObject *pName, *pModule, *py_results;
PyObject *fill_api;
#define PYVERIFY(exp) if ((exp) == 0) { fprintf(stderr, "%s[%d]: ", __FILE__, __LINE__); PyErr_Print(); exit(1); }
Py_SetProgramName(argv[0]); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString(
"import sys;"
"sys.path.insert(0, '.')" );
PYVERIFY( pName = PyString_FromString("interface") )
PYVERIFY( pModule = PyImport_Import(pName) )
Py_DECREF(pName);
PYVERIFY( fill_api = PyObject_GetAttrString(pModule, "fill_api") )
// "k" = [unsigned long],
// see https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
PYVERIFY( py_results = PyObject_CallFunction(fill_api, "k", &api) )
assert(py_results == Py_None);
// Call Python function from C using cffi.
printf("sum: %f\n", api.add_numbers(12.3, 45.6));
// More complex example.
char buffer[20];
char * result = api.dump_buffer(buffer, sizeof buffer);
assert(result != 0);
printf("buffer: %s\n", result);
// Let Python perform garbage collection on result now.
rc = api.release_object(result);
assert(rc == 0);
// Close Python interpreter.
Py_Finalize();
return 0;
}
interface.py
import cffi
import sys
import traceback
ffi = cffi.FFI()
ffi.cdef(file('interface.h').read())
# Hold references to objects to prevent garbage collection.
noGCDict = {}
# Add two numbers.
# This function was copied from the PyPy example.
@ffi.callback("double (double, double)")
def add_numbers(x, y):
return x + y
# Convert input buffer to repr(buffer).
@ffi.callback("char *(char*, int)")
def dump_buffer(buffer, buffer_len):
try:
# First attempt to access data in buffer.
# Using the ffi/lib objects:
# http://cffi.readthedocs.org/en/latest/using.html#using-the-ffi-lib-objects
# One char at time, Looks inefficient.
#data = ''.join([buffer[i] for i in xrange(buffer_len)])
# Second attempt.
# FFI Interface:
# http://cffi.readthedocs.org/en/latest/using.html#ffi-interface
# Works but doc says "str() gives inconsistent results".
#data = str( ffi.buffer(buffer, buffer_len) )
# Convert C buffer to Python str.
# Doc says [:] is recommended instead of str().
data = ffi.buffer(buffer, buffer_len)[:]
# The goal is to return repr(data)
# but it has to be converted to a C buffer.
result = ffi.new('char []', repr(data))
# Save reference to data so it's not freed until released by C program.
noGCDict[ffi.addressof(result)] = result
return result
except:
print >>sys.stderr, traceback.format_exc()
return ffi.NULL
# Release object so that Python can reclaim the memory.
@ffi.callback("int (char*)")
def release_object(ptr):
try:
del noGCDict[ptr]
return 0
except:
print >>sys.stderr, traceback.format_exc()
return 1
def fill_api(ptr):
global api
api = ffi.cast("struct API*", ptr)
api.add_numbers = add_numbers
api.dump_buffer = dump_buffer
api.release_object = release_object
Скомпилировать:
gcc -o test_cffi test_cffi.c -I/home/jmudd/pgsql-native/Python-2.7.10.install/include/python2.7 -L/home/jmudd/pgsql-native/Python-2.7.10.install/lib -lpython2.7
Выполнить:
$ test_cffi
sum: 57.900000
buffer: 'T\x9e\x04\x08\xa8\x93\xff\xbf]\x86\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00'
$