Pull to refresh

Comments 44

Совсем недавно для одного своего проекта на python делал динамическую библиотеку, чтобы вынести в нее нагруженный двойной цикл, который сильно тормозил python, будучи написанным в нем, и отрабатывающий практически мгновенно в библиотеке. Так вот, чтобы не возиться с decode и encode для строк, можно использовать в плюсах тип wchar_t, а в ctypes python тип c_wchar_p, для указателя на строку wchar_t. К такому типу третий python автоматически приводит строки utf-8.
А вы не пробовали для этих целей использовать Numba. С простыми двойными циклами она должна справляться очень хорошо.
Ну я бы не сказал, что он был простым, нужно было работать с двумерным списком, плюс еще два одномерных списка, плюс вызов несколько вспомогательных функций. Также я реализовал был даже многопоточность для этого цикла, с помощью пула multiprocessing? но, к сожалению, там распараллеливание не помогало, сам python сильно долго обходил цикл. А вот передача всех данных в dll позволило мгновенно обработать все данные, даже несмотря на необходимость преобразования классов объектов в структуры ctypes. Даже была идея написать сюда маленькую статейку как создавать двухмерные списки структур для передачи в dll? но подумал что специалисты с хабра и так это знают и напишут зачем я публикую очевидное из документации. Хотя я сам прилично успел голову поломать, когда разбирался со всеми особенностями этого действия.
Мне было бы интересно поглядеть на реализацию. Со своей стороны я бы попробовал это реализовать на cython и numba, потом оценить насколько все это хорошо работает и насколько красивое решение получилось.
Хорошо, я попробую выдрать этот кусок кода из проекта и организовать его отдельное функционирование. Но мне понадобиться некоторое время, так как там используется множество различных объектов. Напишу обертки генераторы всего, что нужно для работы цикла. Как будет готово, я вам отпишусь.
За статью спасибо, хотелось иметь по теме прямо такую справку: простую, понятную, но не упрощенную.
П. С. А то везде пишут, что это просто, но нигде толком не пишут КАК. Мне еще это долго не понадобится, но теперь есть уверенность, что в случае чего — все произойдет быстро
Мне самому это год назад понадобилось и не думаю, что в скором времени нужно будет ). Но решил написать такие readme на будущее, не только для себя.
А вот сегодня идет Data Clustering Contest от Telegram. Прямо сейчас курю заметочку ;) Нужда пришла. Спасибо, человек.

Через ctypes можно вызывать с++ и без extern C. Но это будет "непереносимый код". Так как name mangling не стандартизован, и каждый компилятор будет генерировать свои экспортируемые имена функций. Если их подсмотреть в библиотеке — их также можно спокойно вызвать.
Под linux посмотреть имена функций можно с помощью nm. Для test.cpp без extern C будет примерно так:


nm -D libtestcpp.so

...
00000000000014cc T _Z12test_ret_intP4testi
00000000000013ab T _Z12test_ret_strP4testPc
00000000000014ee T _Z15test_ret_doubleP4testd
0000000000001370 T _Z8test_newv
...

И вот они, имена функций.


Под Windows можно посмотреть с помощью link /dump /exports libtestcpp.dll

Хотел про nm писать, но не стал. Когда makefile писал, то накосячил и libtestpp.so собирал из test.c. С помощью nm разбирался, почему не находит вызываемые функции.

Про то что без extern прокатит писать не стал, а вы не поленились ). Спасибо.
Ни когда бустом не пользовался. Может плохо, может хорошо, может пора и на него посмотреть )
А чем он лучше для вызова обычных C ф-ций?
Мне кажется проще Ctypes уже нет

Для обычных C функций лучше CFFI.
https://qr.ae/TWy0op


Boost.Python я вообще не рекомендую использовать ни для чего. Монстроузный и неудобный (удобнее, конечно, чем голый CPython API, но значительно менее удобный чем более современные штуки).

Думаю, не плохо было б добавить описание работы со structure и union, кроме базовых типов.
Сделал структуру, передача и получение. С получением помучился…
Как-то вы довольно сложно сделали работу со структурами и ее возвращение. Я как-то делал гораздо проще. Посмотрю свои исходники, попробую привести пример.
Как полученные данные скопировать в python структуру напрямую не додумался, кто знает напишите.

Вот что меня смутило в вашем коде, буфер и копирование данных из C структуры в python/ Зачем лишние буфера, операции копирования и прочее. Так как dll может оперировать со структурами ctypes, созданными в самом python, так же можно оперировать структурами, созданными в dll, из самого python.
Вот вы в своей функции вернули тоже, что получили на вход. Не знаю насколько это нужно, поэтому упрощу вашу функцию. Пусть она на вход ничего не получает, а возвращает структуру, созданную в ней.
Тогда в C:
test_st_t *
func_ret_struct(void) {
    test_st_t *res = new test_st_t;
    res->val1 = 19;
    res->val2 = 3.5;
    res->val3 = 'z';
    return res;
}

В коде python:
test.func_ret_struct.argtypes = [ctypes.c_void_p]
test.func_ret_struct.restype = ctypes.POINTER(test_st_t)
ret = test.func_ret_struct()
print('val1 = {}\nval2 = {}\nval3 = {}'.format(ret.contents.val1, ret.contents.val2, ret.contents.val3))

Волшебное слово contents позволяет получить прямой доступ к данным структуры по указателю.
Ну и конечно не забыть потом передать этот указатель в dll на удаление.
Постарался максимально приблизить к вашему примеру, но если где есть неточности, думаю поправите.

Всё это здорово, конечно, но на самом деле это уже давно не нужно, потому что есть pybind11 и CFFI. Эти инструменты разительно упрощают embedding/extending для Python без возни с медленным ctypes.

Мне это понадобилось когда проект был почти готов, поэтому смысла что-то еще делать не было. Шел по легкому пути.
Как полученные данные скопировать в python структуру напрямую не додумался, кто знает напишите.

Возможно можно сделать по аналогии как здесь через ctypes.cast(). Но пока нет возможности проверить

Стоит наверно еще сказать про выравнивание структур. Не так давно столкнулся с такой особенностью.
class GPIO_InitTypeDef(ctypes.Structure):
    _fields_ = [
        ('GPIO_Pin',ctypes.c_uint16),
        ('GPIO_Speed',ctypes.c_uint8),
        ('GPIO_Mode',ctypes.c_uint8)]
ctypes.sizeof(GPIO_InitTypeDef)

sizeof возвращает размер 4 байта.

А С++ библиотека собрана с выравниванием структур по 4 байта, т.е. итоговый размер будет 12
typedef struct
{
  uint16_t GPIO_Pin; 
  GPIOSpeed_TypeDef GPIO_Speed; 
  GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;

sizeof(GPIO_InitTypeDef) // 12 bytes

И можно наткнуться на очень неприятные баги)
Спасибо, мы как раз с автором этой статьи переписываемся. Он показал как делать. Попозже у себя поправлю.
А где освобождается память выделнная в ф-ии test_ret_str(test *test, char *val) ???
Надо поправить статью в части последнего python-скрипта, имя которого отсутствует (я его назвал main_cpp.py). Из-за отсутствия следующих строчек вылетает «Ошибка сегментирования» в stdout (сам функционал работает):
testpp.test_del.restype = ctypes.c_int
testpp.test_del.argtypes = [ctypes.c_void_p]

перед концом файла main_cpp.py:
# Удаляем класс
testpp.test_del(test)

То же самое касается описания работы с переменными:
# Указываем, что функция возвращает int
testpp.test_get_a.restype = ctypes.c_int
testpp.test_get_a.argtypes = [ctypes.c_void_p]
# Указываем, что функция возвращает double
testpp.test_get_b.restype = ctypes.c_double
testpp.test_get_b.argtypes = [ctypes.c_void_p]
# Указываем, что функция возвращает char
testpp.test_get_c.restype = ctypes.c_char
testpp.test_get_c.argtypes = [ctypes.c_void_p]
Тогда пошел автору указывать на необходимость дописать. В исходниках те же пропуски. В целом ничего страшного, даже я смог дополнить)))
Автор я. Пришли мне то что исправил, посмотрю.
Спасибо за статью. Скажите пожалуйста, зачем вот здесь…

# Указываем, что функция принимает аргумент char *
test.func_ret_str.argtypes = [ctypes.POINTER(ctypes.c_char), ]


(ctypes.c_char), ]

… в самом конце запятая?

2 причины, либо ctrl-c & ctrl-v от куда-то делал, либо опечатался.
В любом случае ее наличие или отсутствие ни на что не влияет. В python для упрощения жизни можно после последнего элемента в массиве и т.п. оставить запятую.

Спасибо.

А не могли бы дополнить статью, или тут в комментах написать про create_string_buffer. В каких случаях это нужно использовать? Там что-то про изменяющиеся или не изменяющиеся данные, но я так и не понял.

Сходу не знаю что это, попозже посмотрю.

Здесь подробно написано для чего функция нужна и как ей пользоваться.

Sign up to leave a comment.

Articles