2011-03-09 17 views
8

Ho lavorato con Cython nel tentativo di interfacciare una libreria scritta in C++. Finora le cose stanno andando abbastanza bene e posso utilizzare efficacemente le funzioni MOST all'interno della libreria. Il mio unico problema si trova nell'implementare i callback. La biblioteca dispone di 4 definizioni di funzioni che sembrano un po 'qualcosa di simile a questo:Cython - implementazione di callback

typedef void (*Function1)(const uint16_t *data, 
         unsigned width, unsigned height); 
void SetCallBack(Function1); 

Quindi, per attuare li ho pensato che avrei fatto qualcosa di simile con Cython:

ctypedef void (*Function1)(unsigned short *data, 
         unsigned width, unsigned height); 
cdef extern from "lib.hpp": 
    void SetCallBack(Function1) 

che in realtà compilato correttamente, tuttavia, Non posso per la vita di me pensare a come implementarlo realmente in modo tale che la richiamata funzioni. Ho cercato di creare una funzione che basta chiamare che, in modo simile a come si farebbe per qualsiasi altra funzione, fino a venire con questo:

def PySetCallBack(Func): 
    SetCallBack(Func) 

ma che mi dà la (prevedibile) di errore:

"Impossibile convertire l'oggetto Python in 'Function1'"

quindi sì, è dove sono. Se qualcuno ha qualche esperienza nell'impostare i callback in Cython, sarei molto grato per qualsiasi tipo di assistenza. Grazie.

Edit: seguito il tuo consiglio, ho creato una funzione intermedia con un CDEF, che assomiglia a questo:

cdef void cSetCallBack(Function1 function): 
    SetCallBack(function) 

Questo mi sembra aver ottenuto ... Closer? Ottenere un errore diverso ora almeno:

error: invalid conversion from ‘void (*)(short unsigned int*, unsigned int, unsigned int)’ to ‘void (*)(const uint16_t*, unsigned int, unsigned int)’ 

Ora, per quanto posso dire quei tipi sono identiche, quindi non riesco a capire cosa sta succedendo.

Edit2: Risolto il problema dichiarando un nuovo typedef:

ctypedef unsigned short uint16_t 

ed usando che come argomento per chiamare, ma a quanto pare che non era in realtà sempre più vicino, ma solo a me di prendere in giro una traccia laterale, poiché quando provo a chiamare quella funzione, ottengo lo stesso errore "Impossibile convertire l'oggetto Python in" Function1 "" di nuovo.

Quindi, sono tornato indietro da dove ho iniziato. L'unica cosa che posso immaginare ora è di lanciare esplicitamente l'oggetto Python come una funzione simile, ma, ad essere onesti, non ho idea di come potrei farlo.

Modificare il terzo: Bene, dopo sezionare la tua risposta ho finalmente capito, e funziona, quindi evviva e quant'altro. Quello che ho finito per fare era creare una funzione come questa:

cdef void cSetCallback(Function1 function): 
    SetCallback(function) 
cdef void callcallback(const_ushort *data, unsigned width, unsigned height): 
    global callbackfunc 
    callbackfunc(data,width,height) 
cSetCallback(callcallback) 
def PySetCallback(callbackFunc): 
    global callbackfunc 
    callbackfunc = callbackFunc 

Così ora l'unico problema è che non è possibile convertire const_ushort * dati in un oggetto Python, ma questo è un altro problema del tutto, quindi credo che questo è risolto, grazie mille.

+0

Cambia 'short unsigned int *' a 'const short unsigned int *'. – aschepler

+1

Per quanto ne so, Cython non ha idea di cosa const sia, e ogni volta che cerco di usarlo, ottengo "Const non è un identificatore di tipo". – Josiah

risposta

5

Se è possibile modificare la libreria di definire:

typedef void (*Function1)(const uint16_t *data, 
          unsigned width, unsigned height, 
          void *user_data); 
void SetCallBack(Function1, void*); 

invece, temo tu sei fuori di fortuna. Se si dispone di void*, si definisce una funzione che chiama un oggetto callable python con argomenti corretti e SetCallBack con questa funzione e il python callable.

Se non è possibile, ma il callback è globale (sembra essere), è possibile creare una variabile globale in cui archiviare l'oggetto python. Di nuovo si creerà una funzione per chiamare l'oggetto python e passare a SetCallBack e il tuo PySetCallback dovrebbe semplicemente impostare il globale e assicurarsi che la funzione corretta sia registrata.

Se la richiamata è specifica per il contesto, ma non si ha modo di passarla a un puntatore di "dati utente", temo che qui non abbiate fortuna.

Conosco Python e C++, ma non cython, quindi non so se è possibile creare la funzione in cython, o se si dovrebbe scrivere in C++.

8

Recentemente sono stato nella situazione in cui dovevo anche interfacciare una libreria C++ esistente con Python usando Cython, facendo un uso intensivo di eventi/callback. Non è stato facile da trovare fonti su questo e vorrei mettere tutto questo insieme qui:

Prima di tutto, la classe involucro C++ di callback (sulla base di 'doppio (METHOD) (void)' prototipo , ma avrebbe potuto essere templatized, dal momento Cython in grado di gestire i modelli):

ALabCallBack.h:

#ifndef ALABCALLBACK_H_ 
#define ALABCALLBACK_H_ 


#include <iostream> 

using namespace std; 

namespace elps { 

//template < typename ReturnType, typename Parameter > 
class ALabCallBack { 
public: 

    typedef double (*Method)(void *param, void *user_data); 

    ALabCallBack(); 
    ALabCallBack(Method method, void *user_data); 
    virtual ~ALabCallBack(); 

    double cy_execute(void *parameter); 

    bool IsCythonCall() 
    { 
     return is_cy_call; 
    } 

protected: 

    bool is_cy_call; 

private: 

    //void *_param; 
    Method _method; 
    void *_user_data; 

}; 


} /* namespace elps */ 
#endif /* ALABCALLBACK_H_ */ 

ALabCallBack.cpp:

#include "ALabCallBack.h" 

namespace elps { 


ALabCallBack::ALabCallBack() { 
    is_cy_call = true; 
}; 

ALabCallBack::~ALabCallBack() { 
}; 

ALabCallBack::ALabCallBack(Method method, void *user_data) { 
    is_cy_call = true; 
    _method = method; 
    _user_data = user_data; 
}; 

double ALabCallBack::cy_execute(void *parameter) 
{ 
    return _method(parameter, _user_data); 
}; 


} /* namespace elps */ 

Dove:

  • 'richiamata' :: Il metodo pattern/convertitore di sparare un Python (= Method) Metodo oggetto dalla C digitato informazioni

  • 'metodo' :: Il metodo efficace passato dall'utente Python (= user_data)

  • 'parametro' :: il parametro da passare al 'metodo'

Ora, abbiamo bisogno di implementare il file .pyx ...

Il nostro prototipo di base:

ctypedef double (*Method)(void *param, void *user_data) 

Poi, forniamo un wrapper Cython per la classe C++:

cdef extern from "../inc/ALabCallBack.h" namespace "elps" : 
    cdef cppclass ALabCallBack: 
     ALabCallBack(Method method, void *user_data) 
     double cy_execute(void *parameter) 

Il metodo di pattern/convertitore da utilizzare per la traduzione C digitato prototipo ad un Python chiamata oggetto:

cdef double callback(void *parameter, void *method): 
    return (<object>method)(<object>parameter) 

Ora incorporiamo queste funzionalità in una classe Cython:

cdef class PyLabCallBack: 
    cdef ALabCallBack* thisptr 

    def __cinit__(self, method): 
     # 'callback' :: The pattern/converter method to fire a Python 
     #    object method from C typed infos 
     # 'method' :: The effective method passed by the Python user 
     self.thisptr = new ALabCallBack(callback, <void*>method) 

    def __dealloc__(self): 
     if self.thisptr: 
      del self.thisptr 

    cpdef double execute(self, parameter): 
     # 'parameter' :: The parameter to be passed to the 'method' 
     return self.thisptr.cy_execute(<void*>parameter) 

Edit: Meglio tipizzazione per la funzione di eseguire: def eseguire => cpdef doppia

Questo è tutto. Chiamatela come fare una cosa del genere:

def func(obj): 
    print obj 
    obj.Test()  # Call to a specific method from class 'PyLabNode' 
    return obj.d_prop 

n = PyLabNode() # Custom class of my own 
cb = PyLabCallBack(func) 
print cb.execute(n) 

Come pitone è implicitamente tipizzato, siamo in grado di accedere alle proprietà dell'oggetto 'obj' legato alla classe dell'oggetto passato come argomento, quando arriva il momento di sparare il callback .

Può essere facilmente adattato per l'implementazione di pura C. Per favore, dimmi se puoi vedere qualsiasi miglioramento possibile per questo (nel mio caso, i perfs sono molto cruciali poiché gli eventi vengono licenziati intensivamente).