Передать массив структур из Python в C - PullRequest
9 голосов
/ 21 февраля 2010

[Обновление: проблема решена! Смотрите нижнюю часть поста]

Мне нужно разрешить разработчикам python передавать массив упакованных данных (в данном случае вершин) в мой API, который представляет собой серию интерфейсов C ++, предоставляемых вручную через Python C API. Первоначально у меня сложилось впечатление, что я использую класс ctypes Structure для такого интерфейса:

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

Где функция, которую я пытаюсь передать, имеет следующую подпись:

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

А оболочка Python выглядит примерно так:

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;
}

Конечно, самая большая проблема, с которой я столкнулся, заключается в следующем: я могу получить PyObject для структуры, но я понятия не имею, как я могу привести его к правильному типу. Приведенный выше код с треском проваливается. Так как же мне разрешить пользователю передавать мне такие данные из Python?

Теперь несколько вещей, которые следует учесть: во-первых, у меня уже есть довольно много написанного слоя Python / C ++, и я совершенно доволен им (я отошел от SWIG, чтобы иметь больше гибкости). Я не хочу перекодировать его, поэтому я бы предпочел решение, которое изначально работает с C API. Во-вторых, я намереваюсь, чтобы структура Vertex была предопределена в моем коде C ++, поэтому я бы предпочел, чтобы у пользователя не было необходимости переопределять его в Python (таким образом сокращается количество ошибок), но я не уверен, как выставить непрерывную структуру, как это. В-третьих, у меня нет причин пытаться структурировать ctypes, не зная другого способа сделать это. Любые предложения приветствуются. Наконец, поскольку это (как вы уже догадались) для графического приложения, я бы предпочел более быстрый метод, чем удобный, даже если более быстрый метод требует немного больше работы.

Спасибо за любую помощь! Я до сих пор разбираюсь в расширениях Python, так что это большая помощь, чтобы получить мнение сообщества по некоторым из самых липких частей.

[РЕШЕНИЕ]

Итак, во-первых, спасибо всем, кто высказал свои идеи. Было много маленьких лакомых кусочков, которые суммировали с возможным ответом. В конце концов, вот что я нашел: предложение Сэма использовать struct.pack в итоге оказалось правильным на деньги. Видя, что я использую Python 3, мне пришлось немного его настроить, но когда все было сказано и сделано, на моем экране появился треугольник:

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)

Теперь мой синтаксический анализ кортежа выглядит так:

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;
}

Обратите внимание, что хотя я не использую переменную len в этом примере (хотя я буду использовать в конечном продукте), мне нужно проанализировать кортеж, используя 'y #' вместо просто 'y', иначе остановка на первом NULL (согласно документации). Также следует учитывать: void * приведение таких типов довольно опасно, поэтому, пожалуйста, загрузите больше ошибок, чем я показываю здесь!

Итак, работа хорошо сделана, счастливого дня, соберись и иди домой, да?

Подождите! Не так быстро! Там БОЛЬШЕ!

Чувствуя себя хорошо, как все это сработало, я решил по прихоти посмотреть, все ли моя предыдущая попытка все-таки обернулась и вернулась к первому фрагменту python в этом посте. (Используя новый код на C, конечно) и ... это сработало! Результаты были идентичны версии struct.pack! Вот Это Да!

Таким образом, это означает, что у ваших пользователей есть выбор, как они будут предоставлять данные такого типа, и ваш код может обрабатывать их без изменений. Лично я собираюсь поощрять метод ctype.Structure, так как я думаю, что он облегчает читабельность, но на самом деле это то, с чем пользователю удобно. (Черт, они могли бы вручную напечатать строку байтов в шестнадцатеричном виде, если бы захотели. Это работает. Я пытался.)

Честно говоря, я думаю, что это наилучший из возможных результатов, поэтому я в восторге. Еще раз спасибо всем и удачи всем, кто сталкивается с этой проблемой!

Ответы [ 2 ]

2 голосов
/ 22 февраля 2010

Не проверено, но вы должны попробовать и сообщить нам, достаточно ли это быстро для ваших нужд.

На стороне Python упаковывайте вершины в строку вместо объекта.

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

В оболочке библиотеки C / python измените ваш PyArgs_ParseTuple, чтобы использовать строку формата "si". Это преобразует вашу строку Python в строку C (char *), которую вы можете затем преобразовать как указатель на вашу векторную структуру. На этом этапе строка C представляет собой поток байтов / слов / значений с плавающей запятой и должна соответствовать тому, что вы ищете.

Удачи!

1 голос
/ 22 февраля 2010

Самое простое, что я могу сделать, это просто избежать этой проблемы и выставить Device_ReadVertex, который принимает x, y, z, u, v и цвет в качестве аргументов. Это имеет очевидные недостатки, например, заставляет программистов Python передавать его вершинам одну за другой.

Если это не достаточно хорошо (кажется, что это не так), то вы можете попробовать определить новый тип Python, как описано здесь . Это немного больше кода, но я думаю, что это «более архитектурный» метод, потому что вы гарантируете, что ваши разработчики Python используют то же определение типа, что и в коде C. Он также обеспечивает немного большую гибкость, чем простая структура (это действительно класс, с возможностью добавления методов и т. Д.), Который, я не уверен, вам действительно нужен, но это может пригодиться позже.

...