Передача двухмерных списков из python в DLL

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет.
Решил несколько дополнить статью C/C++ из Python.
Передача стандартных типов, таких как int, bool, float и так далее довольно проста, но мало необходима. С такими данными быстро справится и сам python, и врядли у кого-то возникнет необходимость вынесения части такого кода в библиотеку C/C++.
А вот передача больших массивов данных, или еще лучше двухмерных массивов данных, или даже двухмерных массивов объектов.
Тут уже все не так очевидно, и есть ряд вещей, которые думаю можно осветить для тех кто хочет существенно ускорить трудные для интерпретатора python участки кода.
Приведенный под катом пример не очень полезный для применения, но думаю достаточный, чтобы осветить все ньюансы данной процедуры.



Сразу приведу исходный код файлов библиотеки.


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


А теперь самое главное. Приведу исходный код python скрипта с описанием важных моментов.


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) * size

Далее создаем результирующий массив.


result = resultType()

А в цикле создаем каждую строку, как одномерный массив.


row = rowType()

Далее во вложенном цикле создаем каждый элемент массива и присваиваем значения структуре из списка объектов python.


row[j] = CItem()
row[j].value = py_list[i][j].value
row[j].name = ctypes.c_wchar_p(py_list[i][j].name)

Затем каждую созданную строку с элементами следует преобразовать к типу указателя на массив объектов и присвоить в ячейку результирующего массива.
Про функцию ctypes.cast() напишу чуть ниже.


result[i] = ctypes.cast(row, ctypes.POINTER(CItem))

Ну и конечно преобразовать весь массив к указателю.


return 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**


А есть ctypes.pointer(). Данная функция возвращает указатель на объект. Например:


item = CItem()
pointer = ctypes.pointer(item)

Не следует их путать, так как смысл у них совершенно разный.


ctypes.cast()


А теперь рассмотрим очень важную функцию ctypes.cast()


Данная функция чем-то схожа со static_cast() из C++.
Она позволяет сделать очень важные приведения.
При создании типа массива, например:


rowType = CItem * 4
row = rowType()

В данном случае row является областью памяти из 4 элементов структур CItem.
Конечно в таком виде мы никак не сможем использовать эти данные. А вот если мы на них используем функцию приведения:


array_pointer = ctypes.cast(row, ctypes.POINTER(CItem))

В данном случае array_pointer уже является указателем на область памяти с 4 структурами CItem.
Первым параметром задается созданная область памяти с элементами массива, а вторым параметром надо указать к какому типу надо привести данную область.


Ну вот вроде осветил основные моменты при передачи с помощью ctypes массивов данных.
Надеюсь данная статья поможет более быстро и полно разобраться с замечательной библиотекой ctypes.


Источник: https://habr.com/ru/post/466575/

Интересные статьи

Интересные статьи

Автор материала, перевод которого мы сегодня публикуем, говорит, что современные люди, жизнь которых переполнена работой, часто забывают писать сообщения своим родным и близким. Он, глядя на то, ...
Всем привет! Кто не хочет иметь роскошь иметь помощника, который всегда прислушивается к вашему звонку, предвидит все ваши потребности и при необходимости принимает меры? Эта роскошь тепер...
Это десятая подборка советов про Python и программирование из моего авторского канала @pythonetc. Предыдущие подборки. 0_0 0_0 — полностью корректное выражение на Python. ...
Целью данного проекта было: Изучение протокола DHCP при работе в сети IPv4 Изучение Python (немножко более чем с нуля ;) ) замена серверу DB2DHCP (мой форк), оригинал здесь, который со...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...