Pregunta

Actualización: ¡Problema resuelto! Ver la parte inferior de la publicación

Necesito permitir que los desarrolladores de Python pasen una variedad de datos empaquetados (en este caso vértices) en mi API, que es una serie de interfaces C ++ expuestas manualmente a través de la API de Python C. Mi impresión inicial con esto es usar la clase de estructura CTypes 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

Donde la función a la que estoy tratando de pasar tiene la siguiente firma:

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

Y el envoltorio de Python se parece a esto:

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 este: puedo recuperar el Pyobject para la estructura, pero no tengo idea de cómo lo lanzaría al tipo correcto. El código anterior falla miserablemente. Entonces, ¿cómo exactamente permitiría que el usuario me pase este tipo de datos de Python?

Ahora, un par de cosas a considerar: primero es que ya tengo bastante mi capa Python/C ++ escrita, y estoy perfectamente contento con ella (me alejé de Swig para poder 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, tengo la intención de que la estructura del vértice esté predefinida en mi código C ++, por lo que preferiría no tener el usuario que deba volver a definirla en el Python (reduce los errores de esa manera), pero estoy No estoy seguro de cómo exponer una estructura contigua como esa. En tercer lugar, no tengo ninguna razón para probar la estructura de Ctypes, aparte de no saber otra forma de hacerlo. Cualquier sugerencia es bienvenida. Finalmente, dado que esto es (como habrá adivinado) para una aplicación de gráficos, preferiría un método más rápido sobre uno conveniente, incluso si el método más rápido requiere un poco más de trabajo.

¡Gracias por cualquier ayuda! Todavía me siento por las extensiones de Python, por lo que es de gran ayuda obtener información de la comunidad en algunas de las partes más pegajosas.

SOLUCIÓN

En primer lugar, gracias a todos los que presentaron sus ideas. Fue una gran cantidad de cositas que se sumaron a la respuesta eventual. Al final, esto es lo que encontré: la sugerencia de Sam de usar Struct.Pack terminó siendo correcto en el dinero. Al ver que estoy usando Python 3, tuve que ajustarlo muy ligeramente, pero cuando todo fue dicho y hecho esto realmente tuvo un triángulo 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 analizador de tupla ahora luciendo así:

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 aunque no uso el len Variable en este ejemplo (aunque en el producto final) necesito analizar la tupla usando 'y#' en lugar de solo 'y' o de lo contrario se detendrá en el primer nulo (según la documentación). También para ser considerado: Void* Elenco como este son bastante peligrosos, así que por favor, cargue más verificación de errores de lo que muestro aquí.

Entonces, trabajo bien hecho, feliz día, empacar y irse a casa, ¿sí?

¡Esperar! ¡No tan rapido! ¡Hay más!

Sintiéndome bien sobre cómo funcionó todo eso, decidí, por capricho, ver si mi intento anterior aún me voló y volví al primer fragmento de Python 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! ¡Guau!

Esto significa que sus usuarios tienen una opción sobre cómo van a proporcionar este tipo de datos, y su código puede manejar sin cambios. Personalmente, voy a alentar el método CType.estructura, ya que creo que hace que sea más fácil una legibilidad, pero realmente es lo que sea que el usuario se sienta cómodo. (Diablos, podrían escribir manualmente una cadena de bytes en Hex si quisieran. Funciona. Lo intenté).

Honestamente, creo que este es el mejor resultado posible, así que estoy extasiado. ¡Gracias a todos de nuevo, y buena suerte a cualquier otra persona que se encuentre con este problema!

¿Fue útil?

Solución

No probado, pero debe intentarlo y hacernos 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 el envoltorio de la biblioteca C/python, modifique su pyargs_parsetuple para usar la cadena de formato "si". Esto convertirá su cadena Python en una cadena C (char*) que luego puede escribir como un puntero a su estructura vectorial. En este punto, la cadena C es un flujo de bytes/palabras/flotadores y debe ser lo que está buscando.

¡Buena suerte!

Otros consejos

Lo más fácil que puedo ver sería evitar el problema por completo y exponer un dispositivo_readvertex que toma en X, Y, Z, U, V y Color como argumentos. Esto tiene inconvenientes obvios, como hacer que los programadores de Python le alimenten los vértices uno por uno.

Si eso no es lo suficientemente bueno (parece probable que no lo sea), entonces podría intentar definir un nuevo tipo de Python como se describe aquí. Es un poco más de código, pero creo que este es el método "más arquitectónicamente sólido", porque se asegura de que los desarrolladores de Python estén utilizando la misma definición de tipo que en el código C. También permite un poco más de flexibilidad que una estructura simple (es realmente una clase, con el potencial de agregar métodos, etc.), que no estoy seguro de que realmente necesite, pero puede ser útil más adelante.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top