2010-02-21 12 views
8

[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!

+0

¿ha considerado usar boost :: python? – Anycorn

+0

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

+0

De acuerdo, las dos son buenas bibliotecas para aquellos que no quieren utilizar la C-API. Simplemente no se ajustaban a mis necesidades. – Toji

Respuesta

2

No probado, pero debe probarlo y háganos saber si es lo suficientemente rápido para sus necesidades.

En el lado de python, empaca los vértices en una cadena en lugar de un objeto.

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

en la envoltura de la biblioteca/C pitón, modifique sus PyArgs_ParseTuple utilizar la cadena de formato "si". Esto convertirá su cadena de pitón en una cadena C (char *) que luego puede encasillar como un puntero a su estructura vectorial. En este punto, la cadena C es una secuencia de bytes/palabras/flotantes y debería ser lo que estás buscando.

¡Buena suerte!

+0

Gracias. Un poco más ruidoso de lo que esperaba, ¡pero lo intentaré! – Toji

+0

Sí, no es la solución más rápida ... pero es bastante independiente del lenguaje, y C realmente es de un nivel lo suficientemente bajo como para que esto realmente funcione. ¡Espero que esto ayude! –

+0

Bueno, vaya, no solo funcionó, sino que también me mostró cómo hacer funcionar mi primer enfoque. ¡Hurra! Muchas gracias y ver mis actualizaciones arriba para los detalles. – Toji

1

Lo más fácil que puedo hacer es simplemente evitar el problema y exponer un Device_ReadVertex que tome x, y, z, u, v y color como argumentos. Esto tiene inconvenientes obvios, como hacer que los programadores de Python lo alimenten vértices uno por uno.

Si eso no es lo suficientemente bueno (parece que no lo es), entonces podría tratar de definir un nuevo tipo de Python como se describe here. Es un poco más código, pero creo que este es el método "más arquitectónicamente sólido", porque te aseguras de que tus desarrolladores de Python usan la misma definición de tipo que tú en el código C. También permite un poco más de flexibilidad que una estructura simple (es realmente una clase, con la posibilidad de agregar métodos, etc.), que no estoy seguro de que realmente necesite, pero podría ser útil más adelante.

+0

Actualmente estoy definiendo aproximadamente 10 o más tipos nuevos como parte de mi API, así que eso no es un problema para mí. La pregunta es realmente cómo el usuario podría proporcionarme esa información como una matriz contigua (especialmente porque con los tipos de python debe tener el encabezado de encabezado de tipo para cada instancia). – Toji

+0

Ah, ya veo. Usted se preocupa mucho por el uso del espacio, así como el uso de la CPU, ¿verdad? Creo que la respuesta estructural que Sam Post da a continuación es tan eficiente en cuanto a espacio como se obtendrá, pero según mi experiencia, el módulo struct tiende a usar una gran cantidad de CPU para empacar y descomprimir. Parece que podrías estar en la mejor pista con la estructura tipográfica. Voy a mirar alrededor para ver si no puedo entender por qué su estructura no está funcionando correctamente. – xitrium

+0

Tengo que preocuparme por el espacio, ya que se trata de datos que se pasarán a DirectX u OpenGL. Aunque puede obtener datos no empaquetados, es mejor que no lo haga porque de lo contrario solo estará llenando su memoria de video con datos no deseados. En cuanto a por qué la estructura no está llegando, lo he resuelto. Actualizaré la pregunta en un momento. – Toji

Cuestiones relacionadas