[Actualización: Problema resuelto! Ver la parte inferior de la publicación]Pase una serie de estructuras de Python a C
Necesito permitir que los desarrolladores de Python pasen una matriz de datos empaquetados (en este caso vértices) a mi API, que es una serie de interfaces C++ expuestas manualmente a través de Python C API. Mi impresión inicial con esto es utilizar la clase ctypes Estructura para permitir una interfaz como esta:
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
Cuando la función que estoy tratando de pasar a la siguiente firma tiene:
void Device::ReadVertices(Vertex* verts, int count);
Y el envoltorio de Python es como la siguiente:
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;
}
por supuesto, el mayor problema que tengo es la siguiente: puedo recuperar el PyObject para la estructura, pero no tengo ni idea de cómo iba a echarlo el tipo correcto El código anterior falla miserablemente. Entonces, ¿cómo voy a permitir que el usuario me pase este tipo de datos desde Python?
Ahora, un par de cosas a tener en cuenta: Primero, ya tengo un poco de mi capa de Python/C++ escrita, y estoy perfectamente satisfecho con ella (me alejé de SWIG para tener más flexibilidad). No quiero volver a codificarlo, por lo que preferiría una solución que funcione con la API C de forma nativa. En segundo lugar, pretendo que la estructura Vertex esté predefinida en mi código C++, por lo que preferiría que el usuario no tenga que volver a definirla en Python (reduce los errores de esa manera), pero estoy no estoy seguro de cómo exponer una estructura contigua como esa. Tercero, no tengo ninguna razón para probar la estructura de los tipos, aparte de no saber otra forma de hacerlo. Cualquier sugerencia es bienvenida. Finalmente, dado que esto es (como habrás adivinado) para una aplicación de gráficos, preferiría un método más rápido que uno conveniente, incluso si el método más rápido requiere un poco más de trabajo.
¡Gracias por cualquier ayuda! Todavía estoy sintiendo mi camino alrededor de las extensiones de Python, por lo que es de gran ayuda obtener información de la comunidad sobre algunas de las partes más complicadas.
[Solución]
Así que en primer lugar, gracias a todos los que lanzó en sus ideas. Fueron muchos pequeños detalles que se sumaron a la respuesta final. Al final, aquí está lo que encontré: la sugerencia de Sam de usar struct.pack terminó siendo justo sobre el dinero. Al ver que estoy usando Python 3, tuve que ajustar muy ligeramente, pero cuando todo estaba dicho y hecho esta realidad tiene un triángulo aparece en mi pantalla:
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 mi tupla análisis ahora que parece esto:
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;
}
Tenga en cuenta que a pesar de que yo no uso la variable len
en este ejemplo (aunque yo en el producto final) que necesita para analizar la tupla usando 'y #' en lugar de sólo 'y' o de lo contrario se detendrá en el primer NULL (según la documentación). También para ser considerado: los lanzamientos de void * como este son bastante peligrosos, ¡así que por favor hagan muchas más comprobaciones de errores que las que muestro aquí!
Entonces, trabajo bien hecho, día feliz, empacar e ir a casa, ¿sí?
¡Espera! ¡No tan rapido! ¡Hay más!
Sintiéndome bien acerca de cómo todo salió bien, decidí, por capricho, ver si mi intento anterior aún explotó y volví al primer fragmento de pitón en esta publicación. (Usando el nuevo código C, por supuesto) y ... ¡funcionó! ¡Los resultados fueron idénticos a la versión struct.pack! ¡Guauu!
Esto significa que los usuarios pueden elegir cómo van a proporcionar este tipo de datos, y su código se puede manejar sin cambios. Personalmente, voy a alentar el método ctype.Structure, ya que creo que facilita la lectura, pero realmente es con lo que el usuario se sienta cómodo. (Diablos, podrían escribir manualmente una cadena de bytes en hexadecimal si quisieran. Funciona. Lo intenté)
Honestamente, creo que este es el mejor resultado posible, así que estoy extasiado. ¡Gracias a todos nuevamente, y buena suerte a cualquier persona que se encuentre con este problema!
¿ha considerado usar boost :: python? – Anycorn
Sí, lo probé antes de probar SWIG. Encontré que SWIG es más fácil de usar (muy poco código adicional y no tienes que compilar la monstruosidad que es boost) con casi el mismo rendimiento. Al final, sin embargo, quería tener más control de lo que SWIG me daba (por ejemplo, SWIG no le daba ninguna forma de exponer las propiedades de Python) y opté por hacer el envoltorio por had, que en realidad resultó bastante sencillo una vez que obtienes lo básico abajo, sin mencionar que mi código ahora es mucho más rápido. :) – Toji
De acuerdo, las dos son buenas bibliotecas para aquellos que no quieren utilizar la C-API. Simplemente no se ajustaban a mis necesidades. – Toji