[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!
hai considerato l'utilizzo di boost :: python? – Anycorn
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
Certo, sono entrambe ottime librerie per coloro che non vogliono appoggiarsi alla C-API. Semplicemente non si adattavano ai miei bisogni. – Toji