2010-02-21 15 views
8

[Aggiornamento: problema risolto! Vedere il fondo del post]Passa array di strutture da Python a C

Devo consentire agli sviluppatori Python di passare una matrice di dati compressi (in questo caso vertici) nella mia API, che è una serie di interfacce C++ esposte manualmente tramite l'API C Python. La mia impressione iniziale con questo è quello di utilizzare la classe ctypes Struttura per consentire un'interfaccia come questa:

class Vertex(Structure): 
_fields_ = [ 
    ('x', c_float), 
    ('y', c_float), 
    ('z', c_float), 
    ('u', c_float), 
    ('v', c_float), 
    ('color', c_int) 
] 

verts = (Vertex * 3)() 
verts[0] = Vertex(0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF) 
verts[1] = Vertex(0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF) 
verts[2] = Vertex(-0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF) 

device.ReadVertices(verts, 3) # This is the interfaces to the C++ object 

Dove la funzione che sto cercando di passare ha la seguente firma:

void Device::ReadVertices(Vertex* verts, int count); 

E l'involucro Python simile a questa:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args) 
{ 
    PyObject* py_verts; 
    int count; 

    if(!PyArg_ParseTuple(args, "Oi", &py_verts, &count)) 
     return NULL; 

    // This Doesn't Work! 
    Vertex* verts = static_cast<Vertex*>(PyCObject_AsVoidPtr(py_verts)); 

    self->device->ReadVertices(verts, count); 

    Py_RETURN_NONE; 
} 

Naturalmente, il più grande problema che ho è questa: posso recuperare il PyObject per la struct, ma non ho idea di come avrei gettarlo ai il tipo corretto Il codice sopra riportato fallisce miseramente. Quindi, come farei esattamente a permettere all'utente di passarmi questo tipo di dati da Python?

Ora, un paio di cose da considerare: il primo è che ho già scritto un bel po 'del mio strato Python/C++, e sono perfettamente soddisfatto (mi sono allontanato da SWIG per avere maggiore flessibilità). Non voglio ricodificarlo, quindi preferirei una soluzione che funziona con l'API C in modo nativo. Secondo, intendo che la struttura di Vertex sia predefinita nel mio codice C++, quindi preferirei che l'utente non debba ridefinirlo in Python (riduce gli errori in questo modo), ma io sono non so come esporre una struttura contigua come quella. Terzo, non ho motivo di provare la struttura dei ctype oltre a non sapere un altro modo per farlo. Qualsiasi suggerimento è benvenuto. Infine, poiché questo è (come si può intuire) per un'applicazione grafica, preferirei un metodo più veloce su uno conveniente, anche se il metodo più veloce richiede un po 'più di lavoro.

Grazie per qualsiasi aiuto! Mi sento ancora a mio agio con le estensioni python, quindi è di grande aiuto ottenere input dalla community su alcune delle parti più appiccicose.

[SOLUZIONE]

Quindi, prima di tutto, grazie a tutti coloro che ha lanciato nelle loro idee. C'erano un sacco di piccoli bocconcini che si aggiungevano alla risposta finale. Alla fine, ecco quello che ho trovato: il suggerimento di Sam di usare struct.pack ha finito per avere ragione sui soldi. Vedendo che sto usando Python 3, ho dovuto modificarlo sempre leggermente, ma quando tutto è stato detto e fatto questo in realtà avuto un triangolo che mostra sul mio schermo:

verts = bytes() 
verts += struct.pack("fffffI", 0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF) 
verts += struct.pack("fffffI", 0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF) 
verts += struct.pack("fffffI", -0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF) 

device.ReadVertices(verts, 3) 

Con la mia tuple analisi ora cercando come questo:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args) 
{ 
    void* py_verts; 
    int len, count; 

    if(!PyArg_ParseTuple(args, "y#i", &py_verts, &len, &count)) 
     return NULL; 

    // Works now! 
    Vertex* verts = static_cast<Vertex*>(py_verts); 

    self->device->ReadVertices(verts, count); 

    Py_RETURN_NONE; 
} 

Nota che, anche se io non uso la variabile len in questo esempio (anche se io nel prodotto finale) ho bisogno di analizzare il tupla usando 'y #' invece di 'y' altrimenti si fermerà al primo NULL (secondo la documentazione). Da considerare anche: void * i cast di questo tipo sono abbastanza pericolosi, quindi per favore fai un maggior numero di errori di controllo di quelli che mostro qui!

Quindi, lavoro ben fatto, buona giornata, fare le valigie e andare a casa, si?

Attendere! Non così in fretta! C'è più!

Sentendosi bene su come tutto ciò ha funzionato, ho deciso, per un capriccio, di vedere se il mio precedente tentativo ancora mi esplodesse e tornassi al primo snippet di python in questo post. (Usando il nuovo codice C, ovviamente) e ... ha funzionato! I risultati erano identici alla versione struct.pack! Wow!

Questo significa che gli utenti hanno una scelta su come fornire questo tipo di dati e che il codice può gestire senza modifiche. Personalmente incoraggerò il metodo ctype.Structure, dal momento che penso che faciliti la leggibilità, ma in realtà è ciò con cui l'utente è a suo agio. (Diamine, potevano scrivere manualmente una stringa di byte in esadecimale se lo volessero. Funziona, ci ho provato.)

Onestamente, penso che questo sia il miglior risultato possibile, quindi sono estasiato. Grazie ancora a tutti, e buona fortuna a tutti coloro che si imbattono in questo problema!

+0

hai considerato l'utilizzo di boost :: python? – Anycorn

+0

Sì, l'ho provato prima di provare SWIG. Ho trovato SWIG più facile da usare (pochissimo codice aggiuntivo e non è necessario compilare la mostruosità che è potenziata) con circa le stesse prestazioni. Alla fine, però, volevo un maggiore controllo rispetto a SWIG (ad esempio, SWIG non dava modo di esporre le proprietà di Python) e quindi ho optato per il wrapper di had, che in realtà si rivelò abbastanza indolore una volta ottenuto le basi in basso, per non parlare del mio codice ora è molto più veloce. :) – Toji

+0

Certo, sono entrambe ottime librerie per coloro che non vogliono appoggiarsi alla C-API. Semplicemente non si adattavano ai miei bisogni. – Toji

risposta

2

Non testato ma si dovrebbe fare un tentativo e farci sapere se è abbastanza veloce per le vostre esigenze.

Sul lato python, impacchettare i vertici in una stringa anziché in un oggetto.

str = "" # byte stream for encoding data 
str += struct.pack("5f i", vert1.x, vert1.y, vert1.z, vert1.u, vert1.v, vert1.color) # 5 floats and an int 
# same for other vertices 

device. ReadVertices(verts, 3) # send vertices to C library

Sulla libreria C/python involucro, di modificare i PyArgs_ParseTuple di utilizzare la stringa di formato "si". Questo convertirà la tua stringa python in una stringa C (char *) che potrai poi tipografare come un puntatore alla tua struttura vettoriale. A questo punto la stringa C è un flusso di byte/parole/float e dovrebbe essere quello che stai cercando.

Buona fortuna!

+0

Grazie. Un po 'clunkier di quanto speravo, ma lo proverò! – Toji

+0

Sì, non è la soluzione più intuitiva ... ma è piuttosto agnostico per il linguaggio, e C è davvero di livello abbastanza basso per far sì che funzioni davvero. Spero che questo ti aiuti! –

+0

Beh, figurati, non solo ha funzionato, ma mi ha anche mostrato come far funzionare il mio primo approccio! Sìì! Grazie mille, e vedere i miei aggiornamenti sopra per i dettagli. – Toji

1

La cosa più semplice che posso fare è evitare semplicemente il problema ed esporre un Device_ReadVertex che accetta x, y, z, u, v e color come argomenti. Questo ha ovvi inconvenienti, come far sì che i programmatori Python lo alimentino verticalmente uno per uno.

Se questo non è abbastanza buono (sembra probabile non lo è), allora si potrebbe provare a definire un nuovo tipo Python come descritto here. È un po 'più codice, ma penso che questo sia il metodo "più architettonicamente valido", perché assicuri che gli sviluppatori Python stiano utilizzando la stessa definizione di tipo del codice C. Inoltre, consente un po 'più di flessibilità rispetto a una semplice struttura (è davvero una classe, con la possibilità di aggiungere metodi, ecc.), Che non sono sicuro che sia effettivamente necessario, ma potrebbe tornare utile in seguito.

+0

Attualmente sto definendo circa 10 nuovi tipi come parte della mia API, quindi non è un problema per me. La domanda è davvero come l'utente possa fornirmi quell'informazione come un array contiguo (specialmente perché con i tipi python devi avere l'overhead dell'header di tipo per ogni istanza.) – Toji

+0

Ah, capisco. Ti preoccupi molto dell'uso dello spazio e dell'utilizzo della CPU, giusto? Penso che la risposta alla struttura che Sam Post fornisce sia lo spazio efficiente che otterrete, ma dalla mia esperienza il modulo struct tende a utilizzare un sacco di CPU per comprimere e decomprimere. Sembra che tu possa essere sulla strada migliore con la struttura ctype. Mi guarderò intorno per vedere se non riesco a capire perché la tua struttura non sta attraversando correttamente. – xitrium

+0

Mi devo preoccupare dello spazio, dato che si tratta di dati che devono essere passati a DirectX o OpenGL. Anche se puoi farcela con i dati non stretti, è meglio se non lo fai perché altrimenti ti riempiresti la memoria video con dati spazzatura. Per quanto riguarda il motivo per cui la struttura non sta arrivando, ho risolto. Aggiornerò la domanda tra un momento. – Toji