2009-06-28 11 views
32

Quiero extender un gran proyecto de C con algunas nuevas funcionalidades, pero realmente quiero escribirlo en Python. Básicamente, quiero llamar al código Python desde el código C. Sin embargo, las envolturas de Python-> C como SWIG permiten OPPOSITE, que está escribiendo C módulos y llamando C desde Python.¿Cómo llamas al código Python desde el código C?

Estoy considerando un enfoque que implica IPC o RPC (no me importa tener múltiples procesos); es decir, hacer que mi componente Pure-Python se ejecute en un proceso separado (en la misma máquina) y que mi proyecto C se comunique con él escribiendo/leyendo desde un socket (o unix pipe). mi componente python puede leer/escribir en el socket para comunicarse. ¿Es ese un enfoque razonable? ¿Hay algo mejor? ¿Como algún mecanismo especial de RPC?

Gracias por la respuesta hasta ahora - Sin embargo, me gustaría centrarme en los enfoques basados ​​en IPC ya que quiero tener mi programa Python en un proceso separado como mi programa C. No quiero insertar un intérprete de Python. ¡Gracias!

+5

¿Cuál es su razón para querer poner el programa en Python en un proceso separado, y no querer incorporar un intérprete de Python? Soy curioso. –

+1

Bueno, si él puede abrirse camino con cadenas de tuberías a Python y luego de vuelta a C cuando está hecho, parece mucho más simple que incrustar un intérprete de Python. Simplemente llamar a una aplicación de Python por separado requerirá 5 minutos de integración si la interfaz es simple (simplemente pase cadenas y cadenas) y estoy seguro de que insertar un intérprete llevará un poco más de 5 minutos – hhafez

+0

Aquí un ejemplo completo https: // desbordamiento de pila.com/a/46441794/5842403 donde puede ver Python incorporado en C, y luego C incrustado en Systemverilog usando DPI. – Joniale

Respuesta

10

Recomiendo el approaches detailed here. Comienza explicando cómo ejecutar cadenas de código Python, a partir de allí detalla cómo configurar un entorno Python para interactuar con su programa C, llamar a funciones Python desde su código C, manipular objetos Python desde su código C, etc.

EDIT: Si realmente desea seguir la ruta de IPC, entonces querrá usar the struct module o mejor aún, protlib. La mayor parte de la comunicación entre un proceso de Python y C gira alrededor de pasar estructuras de uno a otro, ya sea over a socket o a través de shared memory.

Recomiendo crear una estructura Command con campos y códigos para representar los comandos y sus argumentos. No puedo dar consejos mucho más específicos sin saber más sobre lo que quiere lograr, pero en general recomiendo la biblioteca protlib, ya que es lo que uso para comunicarme entre los programas C y Python (descargo de responsabilidad: soy el autor de protlib) .

+1

He hecho esto antes y funcionó bien. Tuve varios procesos C que se comunicaban al enviar estructuras sobre un socket y también quería permitir procesos de python. Escribir el protocolo fue trivial en Python y pude escribir un script Python que se ejecutó como parte de la compilación para autocrear el código Python para empaquetar/descomprimir las estructuras C analizando los archivos .H. El único problema es que debido a todo el empaque/desempaquetado de cuerdas, el rendimiento no es casi lo que se obtendría con el C nativo actuando directamente en la representación binaria de las estructuras, simplemente se copia y se retiran los datos brutos del socket. – bdk

4

¿Ha considerado simplemente envolver su aplicación python en un script de shell e invocarlo desde en su aplicación C?

No es la solución más elegante, pero es muy simple.

+0

De hecho, si quiere ejecutar Python en un proceso separado y hablar con él a través de stdin/stdio, esa es casi la mejor solución. – Crashworks

1

No he usado un enfoque IPC para Python < -> C comunicación, pero debería funcionar bastante bien. Quisiera que el programa C hiciera un fork-exec estándar y utilizara los redireccionamientos stdin y stdout en el proceso hijo para la comunicación. Una buena comunicación basada en texto hará que sea muy fácil desarrollar y probar el programa Python.

+0

¿Es posible ejecutar fork-exec en Windows? No estoy seguro de cuál es la plataforma del que pregunta. –

+0

Hay una forma (apenas) documentada de ejecutar fork-exec en Windows, pero no lo haría allí. Mencionó los zócalos de Unix, así que supongo que el objetivo no es Windows. Supongo que sería más como spawn y pipe en Windows. –

1

Si hubiera decidido ir con IPC, probablemente derrocharé con XML-RPC - multiplataforma, le permite poner fácilmente el proyecto de servidor Python en un nodo diferente más adelante si lo desea, tiene muchas implementaciones excelentes (consulte here para muchos, incluidos C y Python, y here para el servidor XML-RPC simple que es parte de la biblioteca estándar de Python, no tan altamente escalable como otros enfoques, pero probablemente sea adecuado para su caso de uso).

Puede que no sea un enfoque IPC perfecto para todos los casos (¡ni siquiera un RPC perfecto, por supuesto!), pero la conveniencia, flexibilidad, solidez y amplia gama de implementaciones superan muchos pequeños defectos, en mi opinión.

0

parecer Python necesita ser capaz de compilar a archivo DLL para Win32, que va a resolver el problema

De tal manera que la conversión de código C# para DLL Win32 hará que sea utilizable por cualquier herramienta de desarrollo

+1

Esto solo se aplica a la plataforma win32 :-p ¿Qué pasa con Unix? – Pharaun

0

Esto parece bastante bueno http://thrift.apache.org/, incluso hay un libro al respecto.

Detalles:

El marco de software Apache Thrift, de entre lenguajes de desarrollo servicios escalable, combina una pila de software con una generación de código motor para construir servicios que funcionan de manera eficiente y sin problemas entre C++, Java , Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml y Delphi y otros idiomas.

0

He utilizado el enfoque "estándar" de Embedding Python in Another Application. Pero es complicado/tedioso. Cada nueva función en Python es dolorosa de implementar.

Vi un ejemplo de Calling PyPy from C. Utiliza CFFI para simplificar la interfaz, pero requiere PyPy, no Python. Lea y comprenda este ejemplo primero, al menos en un nivel alto.

Modifiqué el ejemplo de C/PyPy para trabajar con Python. A continuación, se indica cómo llamar a Python desde C usando CFFI.

Mi ejemplo es más complicado porque implementé tres funciones en Python en lugar de una. Quería cubrir aspectos adicionales de pasar datos de ida y vuelta.

La parte complicada ahora está aislada para pasar la dirección de api a Python. Eso solo tiene que implementarse una vez. Después de eso, es fácil agregar nuevas funciones en Python.

interface.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); 
}; 

test_cffi.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 

de compilación:

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 

Ejecutar:

$ 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' 
$ 
Cuestiones relacionadas