Domanda

Sto appena iniziando con ctypes e vorrei usare una classe C ++ che ho esportato in un file dll da Python usando ctypes. Quindi diciamo che il mio codice C ++ è simile al seguente:

class MyClass {
  public:
    int test();
...

Vorrei creare un file DLL che contiene questa classe e quindi caricare il file DLL in Python usando Ctypes. Ora come potrei creare un oggetto di tipo MyClass e chiamarne la funzione di test? È possibile anche con i tipi? In alternativa, prenderei in considerazione l'utilizzo di SWIG o Boost.Python, ma i ctypes sembrano l'opzione più semplice per i piccoli progetti.

È stato utile?

Soluzione

Il racconto è che non esiste un'interfaccia binaria standard per C ++ come lo è per C. Diversi compilatori producono binari diversi per le stesse librerie dinamiche C ++, a causa della modifica dei nomi e di modi diversi di gestire lo stack tra le librerie chiamate di funzione.

Quindi, sfortunatamente, non esiste davvero un modo portatile per accedere alle librerie C ++ in generale . Ma, per un compilatore alla volta, non è un problema.

Questo post sul blog ha anche una breve panoramica del perché attualmente non funzionerà. Forse dopo l'uscita di C ++ 0x, avremo un ABI standard per C ++? Fino ad allora, probabilmente non avrai modo di accedere alle classi C ++ tramite i ctypes di Python.

Altri suggerimenti

Oltre a Boost.Python (che è probabilmente una soluzione più amichevole per progetti più grandi che richiedono il mapping uno-a-uno delle classi C ++ su classi python), potresti fornire sul lato C ++ un'interfaccia C. È una soluzione di molti, quindi ha i suoi compromessi, ma lo presenterò a beneficio di coloro che non hanno familiarità con la tecnica. Per una completa divulgazione, con questo approccio non si dovrebbe interfacciare C ++ a Python, ma da C ++ a C a Python. Di seguito ho incluso un esempio che soddisfa i tuoi requisiti per mostrarti l'idea generale dell'esterno "c". facilità di compilatori C ++.

//YourFile.cpp (compiled into a .dll or .so file)
#include <new> //For std::nothrow
//Either include a header defining your class, or define it here. 

extern "C"  //Tells the compile to use C-linkage for the next scope.
{
    //Note: The interface this linkage region needs to use C only.  
    void * CreateInstanceOfClass( void )
    {
        // Note: Inside the function body, I can use C++. 
        return new(std::nothrow) MyClass;
    }

    //Thanks Chris. 
    void DeleteInstanceOfClass (void *ptr)
    {
         delete(std::nothrow) ptr; 
    }

    int CallMemberTest(void *ptr)
    {

        // Note: A downside here is the lack of type safety. 
        // You could always internally(in the C++ library) save a reference to all 
        // pointers created of type MyClass and verify it is an element in that
        //structure. 
        //
        // Per comments with Andre, we should avoid throwing exceptions.  
        try
        {
            MyClass * ref = reinterpret_cast<MyClass *>(ptr);
            return ref->Test();
        }
        catch(...)
        {
           return -1; //assuming -1 is an error condition. 
        }
    }

} //End C linkage scope.

Puoi compilare questo codice con

gcc -shared -o test.so test.cpp
#creates test.so in your current working directory.

Nel tuo codice Python potresti fare qualcosa del genere (mostrato il prompt interattivo da 2.7):

>>> from ctypes import cdll
>>> stdc=cdll.LoadLibrary("libc.so.6") # or similar to load c library
>>> stdcpp=cdll.LoadLibrary("libstdc++.so.6") # or similar to load c++ library
>>> myLib=cdll.LoadLibrary("/path/to/test.so")
>>> spam = myLib.CreateInstanceOfClass()
>>> spam
[outputs the pointer address of the element]
>>> value=CallMemberTest(spam)
[does whatever Test does to the spam reference of the object] 

Sono sicuro che Boost.Python fa qualcosa di simile sotto il cofano, ma forse è utile comprendere i concetti di livello inferiore. Sarei più entusiasta di questo metodo se si stesse tentando di accedere alla funzionalità di una libreria C ++ e non fosse necessario un mapping uno a uno.

Per ulteriori informazioni sull'interazione C / C ++, consulta questa pagina da Sun: http : //dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

La risposta di AudaAero è molto buona ma non completa (almeno per me).

Sul mio sistema (Debian Stretch x64 con GCC e G ++ 6.3.0, Python 3.5.3) ho segfaults non appena ho chiamato una funzione membro che accede a un valore membro della classe. Ho diagnosticato stampando i valori del puntatore su stdout che il puntatore void * codificato su 64 bit nei wrapper viene rappresentato su 32 bit in Python. Pertanto, si verificano grossi problemi quando viene restituito a un wrapper di funzioni membro.

La soluzione che ho trovato è cambiare:

spam = myLib.CreateInstanceOfClass()

In

Class_ctor_wrapper = myLib.CreateInstanceOfClass
Class_ctor_wrapper.restype = c_void_p
spam = c_void_p(Class_ctor_wrapper())

Quindi mancavano due cose: impostare il tipo restituito su c_void_p (il valore predefinito è int) e quindi creare un oggetto c_void_p (non solo un numero intero).

Vorrei poter aver scritto un commento ma mi mancano ancora 27 punti rep.

Questa è una breve spiegazione su come usare c e c ++ con C_types in Python. Come scrivere una DLL / SO in C ++ per Python

Estensione di AudaAero's e Gabriel Devillers risposta Vorrei completare la creazione dell'istanza dell'oggetto classe tramite: STDC = c_void_p (cdll.LoadLibrary (" libc.so.6 ")) l'utilizzo del tipo di dati ctypes c_void_p garantisce la corretta rappresentazione del puntatore all'oggetto classe in Python.

Assicurati anche che la gestione della memoria della dll sia gestita dalla dll (la memoria allocata nella dll dovrebbe essere deallocata anche nella dll, e non in python)!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top