Передать массив структур из Python в C
-
22-09-2019 - |
Вопрос
[Обновлять:Задача решена!Смотрите нижнюю часть поста]
Мне нужно разрешить разработчикам Python передавать массив упакованных данных (в данном случае вершин) в мой API, который представляет собой серию интерфейсов C++, предоставляемых вручную через API Python C.Мое первое впечатление заключается в использовании класса структуры ctypes, позволяющего создать такой интерфейс:
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, так как считаю, что он облегчает читаемость, но на самом деле это то, что удобно пользователю.(Черт возьми, они могли бы вручную набрать строку байтов в шестнадцатеричном формате, если бы захотели.Оно работает.Я пытался.)
Честно говоря, я думаю, что это лучший возможный результат, поэтому я в восторге.Еще раз спасибо всем и удачи всем, кто столкнется с этой проблемой!
Решение
Не тестировалось, но вам следует попробовать и сообщить нам, достаточно ли это быстро для ваших нужд.
На стороне 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 представляет собой поток байтов/слов/числов с плавающей запятой и должна быть тем, что вы ищете.
Удачи!
Другие советы
Самое простое, что я могу сделать, — это вообще избежать этой проблемы и предоставить Device_ReadVertex, который принимает x, y, z, u, v и цвет в качестве аргументов.У этого решения есть очевидные недостатки: например, программистам Python приходится передавать ему вершины одну за другой.
Если этого недостаточно (вероятно, это не так), вы можете попробовать определить новый тип Python, как описано. здесь.Это немного больше кода, но я думаю, что это «более архитектурно обоснованный» метод, поскольку вы гарантируете, что ваши разработчики Python используют то же определение типа, что и вы в коде C.Это также обеспечивает немного большую гибкость, чем простая структура (на самом деле это класс с возможностью добавления методов и т. д.), что, я не уверен, вам действительно нужно, но может пригодиться позже.