تمرير مجموعة من الهياكل من بيثون إلى ج
-
22-09-2019 - |
سؤال
تحديث: حل المشكلة! انظر أسفل المنشور
أحتاج إلى السماح لمطوري Python بتمرير مجموعة من البيانات المعبأة (في هذه الحالة) في واجهة برمجة التطبيقات الخاصة بي ، وهي سلسلة من واجهات C ++ المكشوفة يدويًا من خلال Python C API. انطباعي الأولي في هذا هو استخدام فئة هيكل 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);
ولف غلاف الثعبان يبدو مثل هذا:
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/C ++ المكتوبة ، وأنا سعيد تمامًا بها (ابتعدت عن Swig حتى أتمكن من الحصول على مزيد من المرونة). لا أرغب في إعادة ترميزها ، لذلك أفضل حلًا يعمل مع API C أصلاً. ثانياً ، أعتزم أن يتم تعريف بنية قمة الرأس مسبقًا في رمز C ++ الخاص بي ، لذلك أفضل ألا يكون لدى المستخدم إعادة تعريفه في الثعبان (يقلل من الأخطاء بهذه الطريقة) ، لكنني أنا لست متأكدًا من كيفية فضح بنية متجاورة من هذا القبيل. ثالثًا ، ليس لدي أي سبب لتجربة بنية Ctypes بصرف النظر عن عدم معرفة طريقة أخرى للقيام بذلك. أي اقتراحات مرحب بها. أخيرًا ، نظرًا لأن هذا (كما قد تكون قد خمنت) لتطبيق الرسومات ، فإنني أفضل طريقة أسرع على طريقة مريحة ، حتى لو كانت الطريقة الأسرع تستغرق المزيد من العمل.
شكرا على اي مساعدة! ما زلت أشعر بملحقات بيثون ، لذلك فهي مساعدة رائعة للحصول على مدخلات المجتمع في بعض الأجزاء الأكثر إلحاحًا.
المحلول
أولاً ، بفضل كل من نصب في أفكارهم. لقد كان الكثير من الحكايات الصغيرة التي أضافت إلى الإجابة النهائية. في النهاية هنا ما وجدته: انتهى اقتراح سام باستخدام 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)
مع تحليل بلدي tuple الآن يبدو هكذا:
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
متغير في هذا المثال (على الرغم من أنني سأفعل في المنتج النهائي) ، فأنا بحاجة إلى تحليل tuple باستخدام "y#" بدلاً من مجرد "y" وإلا فإنه سيتوقف عند الفارغ الأول (وفقًا للوثائق). أيضا أن يتم النظر فيها: الفراغ* يلقي مثل هذا أمر خطير للغاية ، لذلك يرجى القيام بالأمواج أكثر فحص الأخطاء أكثر مما أظهر هنا!
لذا ، أيها الوظيفة أحسنت ، يوم سعيد ، حزم والعودة إلى المنزل ، نعم؟
انتظر! ليس بهذه السرعة! هناك المزيد!
لقد شعرت بالرضا عن كيفية عمل كل ذلك ، قررت ، على نزوة ، لمعرفة ما إذا كانت محاولتي السابقة لا تزال تنفجر علي وعادت إلى المقتطف الأول من بيثون في هذا المنشور. (باستخدام رمز 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
على غلاف المكتبة/Python ، قم بتعديل pyargs_parsetuple لاستخدام سلسلة التنسيق "si"
. سيؤدي ذلك إلى تحويل سلسلة Python الخاصة بك إلى سلسلة C (Char*) والتي يمكنك بعد ذلك typecast كمؤشر إلى بنية المتجه الخاصة بك. عند هذه النقطة ، تكون السلسلة C عبارة عن تيار من البايت/الكلمات/العوامات ويجب أن تكون ما تبحث عنه.
حظا طيبا وفقك الله!
نصائح أخرى
أسهل شيء يمكنني رؤيته هو تجنب المشكلة تمامًا وفضح جهاز _readvertex الذي يأخذ في x و y و z و u و v واللون كوسائط. هذا له عيوب واضحة ، مثل جعل مبرمجي Python يطعمها رؤوسها واحدة تلو الأخرى.
إذا لم يكن ذلك جيدًا بما يكفي (يبدو أنه ليس كذلك) ، فيمكنك محاولة تحديد نوع Python جديد كما هو موضح هنا. إنه رمز أكثر قليلاً ، لكنني أعتقد أن هذه هي طريقة "الصوت الأكثر معماريًا" ، لأنك تضمن أن مطوري Python الخاص بك يستخدمون نفس تعريف النوع كما أنت في رمز C. كما أنه يتيح مرونة أكثر قليلاً من البنية البسيطة (إنه حقًا فئة ، مع إمكانية إضافة طرق ، إلخ) ، والتي لست متأكدًا من أنك تحتاجها بالفعل ولكنها قد تكون مفيدة لاحقًا.