Pergunta

Eu quero estender um grande projeto C com algumas novas funcionalidades, mas eu realmente quero escrever em Python. Basicamente, eu quero chamar código Python a partir do código C. No entanto, wrappers Python-> C como SWIG permitir o contrário, que está escrevendo módulos C e chamando C do Python.

Eu estou considerando uma abordagem envolvendo IPC ou RPC (eu não me importo de ter múltiplos processos); isto é, tendo minha corrida puro-componente Python em um processo separado (na mesma máquina) e tendo o meu projecto C comunicar com ele pela escrita / leitura a partir de uma tomada (ou tubo UNIX). meu componente python pode ler / escrever à tomada para se comunicar. É que uma abordagem razoável? Existe algo melhor? Como algum mecanismo RPC especial?

Obrigado pela resposta até agora - No entanto, eu gostaria de se concentrar em abordagens baseadas em IPC desde que eu quero ter o meu programa de Python em um processo separado, como o meu programa C. Eu não quero incorporar um interpretador Python. Obrigado!

Foi útil?

Solução

Eu recomendo o abordagens detalhadas aqui . Ele começa por explicar como executar seqüências de código Python, então a partir daí detalha como configurar um ambiente de Python para interagir com o seu programa C, chamar funções Python do seu código C, manipular objetos Python do seu código C, etc.

Editar : Se você realmente quer ir a rota de IPC, então você vai querer usar struct módulo ou melhor ainda, protlib . Mais comunicação entre um pitão e C gira em torno de processo que passa estruturas para trás e para a frente, quer através de um socket ou através memória compartilhada.

Eu recomendo criar um struct Command com campos e códigos para representar comandos e seus argumentos. Eu não posso dar conselhos muito mais específico sem saber mais sobre o que você quer realizar, mas, em geral, eu recomendo o protlib biblioteca, já que é o que eu uso para a comunicação entre programas em C e Python (disclaimer: Eu sou o autor de protlib).

Outras dicas

Veja o capítulo relevante no manual: http://docs.python.org/extending/

Essencialmente, você vai ter que incorporar o interpretador Python em seu programa.

Você considerou apenas envolvendo sua aplicação python em um shell script e invocando-lo de com a sua aplicação C?

Nem a solução mais elegante, mas é muito simples.

Eu não usei uma abordagem IPC para Python <-> comunicação C, mas deve funcionar muito bem. Eu teria o programa C fazer um fork-exec padrão e uso redirecionado stdin e stdout no processo filho para a comunicação. A comunicação baseada em texto agradável irá torná-lo muito fácil de desenvolver e testar o programa Python.

Se eu tinha decidido ir com o IPC, eu provavelmente alarde com XML-RPC - multi-plataforma, permite-lhe colocar facilmente o projeto de servidor de Python em um nó diferente mais tarde, se quiser, tem muitas implementações excelentes (ver aqui para muitos, inclusive C e Python, e aqui para o servidor XML-RPC simples que é parte da biblioteca padrão Python -. não como altamente escalável como outras abordagens, mas provavelmente muito bem e conveniente para seu caso de uso)

Pode não ser uma abordagem perfeita IPC para todos os casos (ou mesmo um perfeito RPC, por todos os meios!), Mas a conveniência, flexibilidade, robustez e ampla gama de implementações superam um monte de pequenos defeitos, na minha opinião.

aparentemente Python necessidade de ser capaz de compilar a DLL Win32, ele vai resolver o problema

Em tal forma a que a conversão de código c # para DLLs Win32 irá torná-lo utilizável por qualquer ferramenta de desenvolvimento

Isso parece bastante agradável http://thrift.apache.org/ , há ainda um livro sobre -lo.

Detalhes:

A estrutura de software Apache Thrift, para cross-language escalável desenvolvimento de serviços, combina uma pilha de software com uma geração de código motor para serviços de construção que trabalham de forma eficiente e sem problemas entre C ++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C #, Cacau, JavaScript, Node.js, Smalltalk, OCaml e Delphi e outras linguagens.

Eu usei a abordagem "standard" de Embedding Python em outro Aplicação. Mas é complicado / tedioso. Cada nova função em Python é doloroso para implementar.

Eu vi um exemplo de Chamando PyPy de C . Ele usa CFFI para simplificar a interface, mas requer PyPy, não Python. Leia e entenda este exemplo em primeiro lugar, pelo menos a um nível elevado.

Eu modifiquei o exemplo C / PyPy para trabalhar com Python. Aqui é como chamar Python de C usando CFFI.

Meu exemplo é mais complicado porque eu implementadas três funções em Python em vez de um. Eu queria cobrir aspectos adicionais de passagem de dados e para trás.

A parte complicada está agora isolado para passar o endereço do api para Python. Que só tem de ser implementada uma vez. Depois disso, é fácil adicionar novas funções em 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

Compilar:

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

Executar:

$ 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'
$ 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top