Comments 44
П. С. А то везде пишут, что это просто, но нигде толком не пишут КАК. Мне еще это долго не понадобится, но теперь есть уверенность, что в случае чего — все произойдет быстро
Через 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
Мне кажется проще Ctypes уже нет
Для обычных C функций лучше CFFI.
https://qr.ae/TWy0op
Boost.Python я вообще не рекомендую использовать ни для чего. Монстроузный и неудобный (удобнее, конечно, чем голый CPython API, но значительно менее удобный чем более современные штуки).
несколько показал работу со Structure
Как полученные данные скопировать в 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 на удаление.
Постарался максимально приблизить к вашему примеру, но если где есть неточности, думаю поправите.
А как же cython? там вообще без танцев с бубном и 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
И можно наткнуться на очень неприятные баги)
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. В каких случаях это нужно использовать? Там что-то про изменяющиеся или не изменяющиеся данные, но я так и не понял.
Сходу не знаю что это, попозже посмотрю.
Здесь подробно написано для чего функция нужна и как ей пользоваться.
удалить этот комент.
C/C++ из Python (ctypes)