تمرير قوائم ثنائية الأبعاد من python إلى DLL

مرحبا بالجميع. قررت أن أستكمل قليلاً مقال C / C ++ من بيثون .


يعد تمرير الأنواع القياسية مثل int و bool و float وما إلى ذلك أمرًا بسيطًا للغاية ، ولكنه ليس ضروريًا للغاية. ستعالج Python نفسها بسرعة مثل هذه البيانات ، ومن غير المرجح أن يحتاج أي شخص إلى نقل جزء من هذا الرمز إلى مكتبة C / C ++.


ولكن نقل صفائف البيانات الكبيرة ، أو حتى صفائف البيانات ثنائية الأبعاد أفضل ، أو حتى صفائف ثنائية الأبعاد للكائنات.


كل شيء هنا غير واضح ، وهناك عدد من الأشياء التي أعتقد أنه يمكن تسليط الضوء عليها لأولئك الذين يرغبون في تسريع أقسام الأكواد بشكل صعب بالنسبة لمترجم بايثون.


المثال الوارد تحت القص ليس مفيدًا جدًا للاستخدام ، لكنني أعتقد أنه يكفي لتسليط الضوء على جميع الفروق الدقيقة في هذا الإجراء.


أعط الكود المصدري لملفات المكتبة على الفور.


py_list2c_array.h


#ifndef _PY_LIST_2_C_ARRAY_H_ #define _PY_LIST_2_C_ARRAY_H_ #include <stdio.h> typedef struct { int value; wchar_t* name; } Item; extern "C" __declspec(dllexport) int sum_diagonal(Item** field, size_t size); #endif 

py_list2c_array.cpp


كل شيء قياسي هنا أيضًا ، إلا أنني لاحظت استخدام وظيفة wprintf () لطباعة خطوط مثل wchar_t * .


 // py_list2c_array.cpp:      DLL. // #include "stdafx.h" #include "py_list2c_array.h" extern "C" __declspec(dllexport) int sum_diagonal(Item** field, size_t size) { int result = 0; for(size_t i=0; i<size;++i) { for(size_t j=0; j<size; ++j) { if(i == j) { result += field[i][j].value; wprintf(L"%s\n", field[i][j].name); } } } return result; } 

py_list2c_array.py


والآن الشيء الأكثر أهمية. سأقدم الكود المصدري لخط بيثون مع وصف النقاط المهمة.


 import ctypes class PyItem: def __init__(self, value, name): self.value = value self.name = name class CItem(ctypes.Structure): _fields_ = [ ('value', ctypes.c_int), ('name', ctypes.c_wchar_p) ] def create_list(size): return [[PyItem(int(str(i+1)+str(j+1)), 'item{}{}'.format(i+1, j+1)) for j in range(size)] for i in range(size)] def py_list2c_array(py_list, size): rowType = CItem * size resultType = ctypes.POINTER(CItem) * size result = resultType() for i in range(size): row = rowType() for j in range(size): row[j] = CItem() row[j].value = py_list[i][j].value row[j].name = ctypes.c_wchar_p(py_list[i][j].name) result[i] = ctypes.cast(row, ctypes.POINTER(CItem)) return ctypes.cast(result, ctypes.POINTER(ctypes.POINTER(CItem))) if __name__ == '__main__': sLib = ctypes.cdll.LoadLibrary('./py_list2c_array.dll') size = 4 py_list = create_list(size) c_array = py_list2c_array(py_list, size) sLib.sum_diagonal.argtypes = [ctypes.POINTER(ctypes.POINTER(CItem)), ctypes.c_size_t] sLib.sum_diagonal.restype = ctypes.c_int result = sLib.sum_diagonal(c_array, ctypes.c_size_t(size)) print(': {}'.format(result)) 

تفاصيل


دعونا نفكر في بعض ميزات إنشاء صفائف ctypes. للقيام بذلك ، سنلقي نظرة فاحصة على وظيفة تحويل القائمة إلى py_list2c_array يجب عليك تحديد الأنواع أولا.


يتم تعريف نوع كل صف في الصفيف على أنه نوع العنصر مضروب في عدد العناصر.


rowType = CItem * size


يتم تعريف نوع الصفيف على أنه نوع الصف الخاص بالصفيف يضاعف عدد الصفوف.


سأشرح قليلاً أدناه حول ctypes.POINTER (). ResultType = ctypes.POINTER (CItem) * الحجم


بعد ذلك ، قم بإنشاء الصفيف الناتج .result = resultType ()


وفي الحلقة ، نقوم بإنشاء كل صف كصفيف أحادي البعد .row = rowType ()


بعد ذلك ، في حلقة متداخلة ، قم بإنشاء كل عنصر من عناصر الصفيف وتعيين قيم للهيكل من قائمة الكائنات python.row [j] = CItem ()


الصف [j] .value = py_list [i] [j] .value
صف [j] .name = ctypes.c_wchar_p (py_list [i] [j] .name)


ثم يجب تحويل كل سطر تم إنشاؤه مع عناصر إلى نوع مؤشر إلى صفيف من الكائنات وتعيينه في خلية الصفيف الناتج.


سأكتب عن وظيفة ctypes.cast () أدناه. result [i] = ctypes.cast (row، ctypes.POINTER (CItem))
وبالطبع ، قم بتحويل الصفيف بأكمله إلى مؤشر. قم بإرجاع ctypes.cast (result ، ctypes.POINTER (ctypes.POINTER (CItem)))


ctypes.POINTER


يحتوي Ctypes على ctypes.POINTER () - يشير إلى أنه يتم استخدام مؤشر. على سبيل المثال: يشير ctypes.POINTER (CItem) إلى أنه مؤشر إلى بنية CItem ().
وفقًا لذلك ، باستخدام السطر: ctypes.POINTER (ctypes.POINTER (CItem)) يمكننا الإشارة إلى أنه مؤشر إلى بنية CItem ، أو في C ++ CItem ** A هناك ctypes.pointer () . هذه الدالة تقوم بإرجاع مؤشر إلى كائن. على سبيل المثال: item = CItem ()


المؤشر = ctypes.pointer (البند)


لا ينبغي الخلط بينهما ، لأن معناها مختلف تمامًا.


ctypes.cast ()


الآن ، ضع في اعتبارك أن الوظيفة ctypes.cast () المهمة جدًا تشبه هذه الوظيفة إلى حد ما static_cast () من C ++.


انها تسمح لك لجعل يلقي مهم جدا.


عند إنشاء نوع صفيف ، على سبيل المثال: rowType = CItem * 4


صف = صف نوع ()


في هذه الحالة ، الصف عبارة عن مساحة ذاكرة مكونة من 4 عناصر بنيات CItem.


بالطبع ، في هذا النموذج ، لن نتمكن من استخدام هذه البيانات. ولكن إذا استخدمنا الدالة cast عليها: array_pointer = ctypes.cast (row، ctypes.POINTER (CItem))


في هذه الحالة ، array_pointer هو بالفعل مؤشر إلى منطقة الذاكرة مع 4 بنيات CItem.


تقوم المعلمة الأولى بتعيين منطقة الذاكرة التي تم إنشاؤها باستخدام عناصر الصفيف ، والمعلمة الثانية تشير إلى النوع الذي يجب تحويل المنطقة إليه. حسنًا ، يبدو أنه يبرز النقاط الرئيسية عند نقل صفائف البيانات باستخدام الأنواع.


آمل أن يساعدك هذا المقال بشكل أسرع وكامل في التعامل مع مكتبة ctypes الرائعة.

Source: https://habr.com/ru/post/ar466575/


All Articles