2012-01-27 13 views
35

Ho un'interfaccia di classe scritta in C++. Ho alcune classi che implementano questa interfaccia anche scritte in C++. Questi sono chiamati nel contesto di un programma C++ più grande, che implementa essenzialmente "main". Voglio essere in grado di scrivere implementazioni di questa interfaccia in Python e permetterle di essere usate nel contesto del più ampio programma C++, come se fossero state appena scritte in C++.Come posso implementare una classe C++ in Python, per essere chiamata da C++?

C'è stato molto scritto sull'interfaccia tra Python e C++ ma non riesco a capire come fare ciò che voglio. Il più vicino che riesco a trovare è qui: http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions, ma non è giusto.

Per essere più concreto, supponiamo di avere un C++ un'interfaccia definita qualcosa di esistente come:

// myif.h 
class myif { 
    public: 
    virtual float myfunc(float a); 
}; 

Quello che voglio essere in grado di fare è qualcosa di simile:

// mycl.py 
... some magic python stuff ... 
class MyCl(myif): 
    def myfunc(a): 
    return a*2 

Poi, di nuovo in il mio codice C++, voglio essere in grado di dire qualcosa del tipo:

// mymain.cc 
void main(...) { 
    ... some magic c++ stuff ... 
    myif c = MyCl(); // get the python class 
    cout << c.myfunc(5) << endl; // should print 10 
} 

Spero che questo sia sufficientemente cle ar;)

+4

"Spero che questo sia sufficientemente chiaro" ... C + + avvolto in python e poi riavvolto in C++? – AJG85

+2

Che cosa esattamente speri di realizzare costruendo questa infrastruttura? –

+3

@ AJG85, non "incapsulato". Chiede di essere in grado di ereditare una classe C++ in Python e quindi di usarla in C++. (huh!) –

risposta

10

Citando http://wiki.python.org/moin/boost.python/Inheritance

"Boost.Python permette inoltre di rappresentare C++ relazioni di ereditarietà in modo che le classi derivate avvolte possono essere passati dove valori, puntatori o riferimenti a una classe di base sono previsti come argomenti"

ci sono esempi di funzioni virtuali in modo che risolve la prima parte (quella con classe MyCl (myif))

Per esempi specifici fare questo, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

Per la linea myif c = MyCl() ; hai bisogno di esporre il tuo python (modulo) a C++. Ci sono esempi qui http://wiki.python.org/moin/boost.python/EmbeddingPython

+0

Puoi espandere questo esempio completo? Ho messo una taglia su di esso se questo ti ispira :) – Flexo

+0

Non ero il downvoter, ma ho il sospetto che la mancanza di dettagli e la natura leggermente speculativa di questa risposta fosse dietro. Questa è certamente la ragione per cui ho offerto la generosità. – Flexo

1

Dai un'occhiata a Boost Python, che è lo strumento più versatile e potente per il bridge tra C++ e Python.

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/

+1

È potente quanto l'API Python C, ma è sicuramente una grande libreria. [Una cosa che sto cercando] (http://stackoverflow.com/q/9050985/627005) e apparentemente non puoi fare con Boost è definire metaclassi Python in C++; puoi farlo con l'API C, però. –

38

Ci sono due parti di questa risposta. Per prima cosa è necessario esporre la tua interfaccia in Python in un modo che permetta alle implementazioni di Python di sovrascrivere parti di esso a piacimento. Allora avete bisogno di mostrare il vostro programma C++ (in main come chiamare Python


Esporre l'interfaccia esistente a Python:.

La prima parte è abbastanza facile da fare con SWIG ho modificato lo scenario esempio. un po 'per risolvere alcuni problemi e ha aggiunto una funzione aggiuntiva per il test:

// myif.h 
class myif { 
    public: 
    virtual float myfunc(float a) = 0; 
}; 

inline void runCode(myif *inst) { 
    std::cout << inst->myfunc(5) << std::endl; 
} 

per ora mi guardo il problema senza incorporare Python nell'applicazione, vale a dire che si avvia excetion in Python, non in int main() in C++. È f facile da aggiungere semplicemente dopo.

Il primo è sempre cross-language polymorphism working:

%module(directors="1") module 

// We need to include myif.h in the SWIG generated C++ file 
%{ 
#include <iostream> 
#include "myif.h" 
%} 

// Enable cross-language polymorphism in the SWIG wrapper. 
// It's pretty slow so not enable by default 
%feature("director") myif; 

// Tell swig to wrap everything in myif.h 
%include "myif.h" 

Per fare che abbiamo attivato la funzione di direttore SWIG a livello globale e in particolare per la nostra interfaccia. Il resto è comunque piuttosto SWIG standard.

ho scritto un'implementazione di test di Python:

import module 

class MyCl(module.myif): 
    def __init__(self): 
    module.myif.__init__(self) 
    def myfunc(self,a): 
    return a*2.0 

cl = MyCl() 

print cl.myfunc(100.0) 

module.runCode(cl) 

Con che ero poi in grado di compilare ed eseguire questo:

 
swig -python -c++ -Wall myif.i 
g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 

python mycl.py 
200.0 
10 

Esattamente quello che ci si speri di vedere da quel test.


Incorporare il Python nell'applicazione:

Next up abbiamo bisogno di implementare una versione reale del vostro mymain.cc. Ho messo insieme un abbozzo di quello che potrebbe essere simile:

#include <iostream> 
#include "myif.h" 
#include <Python.h> 

int main() 
{ 
    Py_Initialize(); 

    const double input = 5.0; 

    PyObject *main = PyImport_AddModule("__main__"); 
    PyObject *dict = PyModule_GetDict(main); 
    PySys_SetPath("."); 
    PyObject *module = PyImport_Import(PyString_FromString("mycl")); 
    PyModule_AddObject(main, "mycl", module); 

    PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict); 
    PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input)); 

    PyObject *error = PyErr_Occurred(); 
    if (error) { 
    std::cerr << "Error occured in PyRun_String" << std::endl; 
    PyErr_Print(); 
    } 

    double ret = PyFloat_AsDouble(result); 
    std::cout << ret << std::endl; 

    Py_Finalize(); 
    return 0; 
} 

E 'fondamentalmente solo standard di embedding Python in another application. Funziona e dà esattamente quello che ci si speri di vedere anche:

 
g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 
./main 
200.0 
10 
10 

L'ultimo pezzo del puzzle è quello di poter convertire la PyObject* che si ottiene dal creare l'istanza in Python in un myif *. SWIG lo rende ancora ragionevolmente semplice.

Per prima cosa dobbiamo chiedere a SWIG di esporre il suo runtime in un file di intestazione per noi. Lo facciamo con una chiamata in più a SWIG:

 
swig -Wall -c++ -python -external-runtime runtime.h 

Poi abbiamo bisogno di ricompilare il nostro modulo di SWIG, dando esplicitamente la tabella dei tipi SWIG conosce un nome in modo che possiamo guardare in alto dall'interno della nostra principale. cc. Abbiamo ricompilare il .so utilizzando:

 
g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 

Poi aggiungiamo una funzione di supporto per la conversione del PyObject* al myif* nel nostro main.cc:

#include "runtime.h" 
// runtime.h was generated by SWIG for us with the second call we made 

myif *python2interface(PyObject *obj) { 
    void *argp1 = 0; 
    swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *"); 

    const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0); 
    if (!SWIG_IsOK(res)) { 
    abort(); 
    } 
    return reinterpret_cast<myif*>(argp1); 
} 

Ora questo è a posto si può usare dall'interno main():

int main() 
{ 
    Py_Initialize(); 

    const double input = 5.5; 

    PySys_SetPath("."); 
    PyObject *module = PyImport_ImportModule("mycl"); 

    PyObject *cls = PyObject_GetAttrString(module, "MyCl"); 
    PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL); 

    myif *inst = python2interface(instance); 
    std::cout << inst->myfunc(input) << std::endl; 

    Py_XDECREF(instance); 
    Py_XDECREF(cls); 

    Py_Finalize(); 
    return 0; 
} 

infine abbiamo compilare main.cc con -DSWIG_TYPE_TABLE=myif e questo dà:

012.351.
 
./main 
11 
+0

Se hai detto che avrebbe funzionato, ti avrei mandato in su :( – Jono

+1

@JonoRR - corretto, stavo usando l'handle del modulo piuttosto che il suo dizionario come argomento di 'PyRun'. – Flexo

+1

Tutti i sorgenti e i comandi richiesti sono contenuti nella mia risposta ma Ho anche messo l'intero lotto online su: http://static.lislan.org.uk/~ajw/pyvirt.tar.gz – Flexo

-1

Non esiste un modo reale di interfacciare il codice C++ direttamente con Python.

SWIG gestisce questo, ma crea il proprio wrapper.

Un'alternativa che preferisco su SWIG è ctypes, ma per utilizzarla è necessario creare un wrapper C.

Per l'esempio:

// myif.h 
class myif { 
    public: 
    virtual float myfunc(float a); 
}; 

Costruire un wrapper C in questo modo:

extern "C" __declspec(dllexport) float myif_myfunc(myif* m, float a) { 
    return m->myfunc(a); 
} 

Dal momento che si sta costruendo in C++, l'extern "C" consente di collegamento C così si può chiamare facilmente dalla tua DLL, e __declspec (dllexport) consente alla funzione di essere chiamata dalla DLL.

In Python:

from ctypes import * 
from os.path import dirname 

dlldir = dirname(__file__)      # this strips it to the directory only 
dlldir.replace('\\', '\\\\')     # Replaces \ with \\ in dlldir 
lib = cdll.LoadLibrary(dlldir+'\\myif.dll')  # Loads from the full path to your module. 

# Just an alias for the void pointer for your class 
c_myif = c_void_p 

# This tells Python how to interpret the return type and arguments 
lib.myif_myfunc.argtypes = [ c_myif, c_float ] 
lib.myif_myfunc.restype = c_float 

class MyCl(myif): 
    def __init__: 
     # Assume you wrapped a constructor for myif in C 
     self.obj = lib.myif_newmyif(None) 

    def myfunc(a): 
     return lib.myif_myfunc(self.obj, a) 

Mentre SWIG fa tutto questo per voi, c'è poco spazio per voi di modificare le cose come ti pare senza essere frustrato per tutte le modifiche si deve rifare quando si rigenera l'involucro SWIG .

Un problema con i tipi ctypes è che non gestisce le strutture STL, dal momento che è fatto per C. SWIG lo gestisce per te, ma potresti essere in grado di avvolgerlo da solo in C. Dipende da te.

Ecco il doc Python per ctypes: (? Perché non sarebbe)

http://docs.python.org/library/ctypes.html

Inoltre, la dll costruita deve essere nella stessa cartella come l'interfaccia di Python.

Sono curioso però, perché dovresti chiamare Python all'interno del C++ invece di chiamare direttamente l'implementazione C++?

+1

* "c'è poco spazio per modificare le cose come preferisci senza essere frustrato da tutte le modifiche che devi ripetere quando rigeneri il wrapper SWIG" * - non dovresti * mai * modificare manualmente un file SWIG generato. C'è sempre un modo per far sì che SWIG generi il codice che vuoi. – Flexo

+0

In termini di abbondanza questa risposta non aggiunge molto che posso vedere - Questo non risolve l'incorporamento e "come faccio a passare un'istanza del mio tipo Python che deriva dall'interfaccia di base a un problema di funzione C++ ". Potrei scrivere un proxy che lo gestisse, ma stavo cercando una soluzione che prevedesse la scrittura di colla * less * o colla più ordinata rispetto a quella SWIG che ho proposto. Questo sembra aggiungere più colla scritta manualmente e non affrontare il problema di incorporamento di Python in C++. – Flexo

12

Esempio minimo; si noti che è complicato dal fatto che Base non è puro virtuale. Ci andiamo:

  1. baz.cpp:

    #include<string> 
    #include<boost/python.hpp> 
    using std::string; 
    namespace py=boost::python; 
    
    struct Base{ 
        virtual string foo() const { return "Base.foo"; } 
        // fooBase is non-virtual, calling it from anywhere (c++ or python) 
        // will go through c++ dispatch 
        string fooBase() const { return foo(); } 
    }; 
    struct BaseWrapper: Base, py::wrapper<Base>{ 
        string foo() const{ 
        // if Base were abstract (non-instantiable in python), then 
        // there would be only this->get_override("foo")() here 
        // 
        // if called on a class which overrides foo in python 
        if(this->get_override("foo")) return this->get_override("foo")(); 
        // no override in python; happens if Base(Wrapper) is instantiated directly 
        else return Base::foo(); 
        } 
    }; 
    
    BOOST_PYTHON_MODULE(baz){ 
        py::class_<BaseWrapper,boost::noncopyable>("Base") 
        .def("foo",&Base::foo) 
        .def("fooBase",&Base::fooBase) 
        ; 
    } 
    
  2. bar.py

    import sys 
    sys.path.append('.') 
    import baz 
    
    class PyDerived(baz.Base): 
        def foo(self): return 'PyDerived.foo' 
    
    base=baz.Base() 
    der=PyDerived() 
    print base.foo(), base.fooBase() 
    print der.foo(), der.fooBase() 
    
  3. Makefile

    default: 
         g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags` 
    

E il risultato è:

Base.foo Base.foo 
PyDerived.foo PyDerived.foo 

dove si può vedere come fooBase() (la funzione non virtuale C++) chiama virtual foo(), che risolve alla sostituzione indipendentemente dal fatto che in C++ o Python. Potresti ottenere una classe da Base in C++ e funzionerebbe allo stesso modo.

EDIT (estrazione C++ oggetto):

PyObject* obj; // given 
py::object pyObj(obj); // wrap as boost::python object (cheap) 
py::extract<Base> ex(pyObj); 
if(ex.check()){ // types are compatible 
    Base& b=ex(); // get the wrapped object 
    // ... 
} else { 
    // error 
} 

// shorter, thrwos when conversion not possible 
Base &b=py::extract<Base>(py::object(obj))(); 

Costruire py::object da PyObject* e utilizzare py::extract per interrogare se l'oggetto Python corrisponde a quello che si sta cercando di estrarre: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();

+0

Questo sembra abbastanza buono - puoi aggiungere l'equivalente Boost.Python che incorpora l'interprete in 'main()' a quello nel mio esempio che usa il 'myif * python2interface (PyObject * obj);' per prendere un PyObject e convertirlo torna a un'implementazione dell'interfaccia C++? (Con quello sarebbe esattamente la risposta che avevo in mente per la taglia) – Flexo

+1

L'ho inserito nella modifica, se è quello che avevi in ​​mente. – eudoxos

+0

Ti ho assegnato la taglia per questo, grazie! Ho anche esteso la tua soluzione per incorporare ed estendere contemporaneamente, che ho pubblicato come seconda risposta. – Flexo

8

Sulla base the (very helpful) answer by Eudoxos I 'ha preso il suo codice e lo ha esteso in modo tale che ora ci sia un interprete incorporato, con un modulo integrato.

Questa risposta è l'equivalente Boost.Python di my SWIG based answer.

Il myif.h headerfile:

class myif { 
public: 
    virtual float myfunc(float a) const { return 0; } 
    virtual ~myif() {} 
}; 

è fondamentalmente come nella questione, ma con un'implementazione di default di myfunc e un distruttore virtuale.

Per l'implementazione di Python, MyCl.py mi hanno fondamentalmente la stessa domanda:

import myif 

class MyCl(myif.myif): 
    def myfunc(self,a): 
    return a*2.0 

Questo lascia quindi mymain.cc, la maggior parte dei quali è basata sulla risposta da Eudosso:

#include <boost/python.hpp> 
#include <iostream> 
#include "myif.h" 

using namespace boost::python; 

// This is basically Eudoxos's answer: 
struct MyIfWrapper: myif, wrapper<myif>{ 
    float myfunc(float a) const { 
    if(this->get_override("myfunc")) 
     return this->get_override("myfunc")(a); 
    else 
     return myif::myfunc(a); 
    } 
}; 

BOOST_PYTHON_MODULE(myif){ 
    class_<MyIfWrapper,boost::noncopyable>("myif") 
    .def("myfunc",&myif::myfunc) 
    ; 
} 
// End answer by Eudoxos 

int main(int argc, char ** argv) { 
    try { 
    // Tell python that "myif" is a built-in module 
    PyImport_AppendInittab("myif", initmyif); 
    // Set up embedded Python interpreter: 
    Py_Initialize(); 

    object main_module = import("__main__"); 
    object main_namespace = main_module.attr("__dict__"); 

    PySys_SetPath("."); 
    main_namespace["mycl"] = import("mycl"); 

    // Create the Python object with an eval() 
    object obj = eval("mycl.MyCl()", main_namespace); 

    // Find the base C++ type for the Python object (from Eudoxos) 
    const myif &b=extract<myif>(obj)(); 
    std::cout << b.myfunc(5) << std::endl; 

    } catch(error_already_set) { 
    PyErr_Print(); 
    } 
} 

La parte fondamentale che ho aggiunto qui, al di sopra e al di là del "come faccio ad incorporare Python usando Boost.Python?" e "come posso estendere Python usando Boost.python?" (che è stato risposto da Eudoxos) è la risposta alla domanda "Come faccio entrambi nello stesso programma?". La soluzione a questo problema è la chiamata PyImport_AppendInittab, che accetta la funzione di inizializzazione che verrebbe normalmente chiamata al momento del caricamento del modulo e la registra come modulo integrato. Quindi quando mycl.py dice import myif finisce per importare il modulo Boost.Python integrato.

+2

Il codice sorgente in questa risposta è completo, ma per praticità ho messo l'intero esempio su http://static.lislan.org.uk/~ajw/boostpyvirt.tar.gz – Flexo

Problemi correlati