Великий и могучий питон…
Правда бывают случаи когда в код на питоне хочется вставить код на си или еще чего. Толи дело в производительности, толи в не желании писать велосипед.
Не суть.
В python есть хороший модуль для работы с разделяемыми библиотеками — ctypes. О нем дальше и речь.
Для работы с внешней dll нужно просто импортировать её и вызвать нужную функцию, заведомо определив нужные типы данных.
Пациентом у меня будет modbus.dll из проекта libmodbus.
Она предоставляет возможность не морочась быть мастером или клиентом в сети Modbus. Для проверки соединения я использую Ananas.
Для работы нужно определить структуры.
struct _modbus { /* Slave address */ int slave; /* Socket or file descriptor */ int s; int debug; int error_recovery; struct timeval response_timeout; struct timeval byte_timeout; const modbus_backend_t *backend; void *backend_data; };
Импортируем ctypes.
После чего описываем структуры.
каждое поле состоит из имени и типа. Все типы ctypes можно найти в wiki питона, но редактор с автоподстановкой исключает эту необходимость.
from ctypes import * class timeval(Structure): _fields_ = [ ("tv_sec", c_long), ("tv_usec",c_long) ] class modbus_t(Structure): _fields_ = [ ("slave", c_int), ("s", c_int), ("debug", c_int), ("error_recovery", c_int), ("response_timeout", timeval), ("byte_timeout", timeval), ("backend", POINTER(modbus_backend_t)), ("backend_data", c_void_p) ]
Также для работы с библиотекой нужна структура, которая содержит указатели на функции.
typedef struct _modbus_backend { unsigned int backend_type; unsigned int header_length; unsigned int checksum_length; unsigned int max_adu_length; int (*set_slave) (modbus_t *ctx, int slave); int (*build_request_basis) (modbus_t *ctx, int function, int addr, int nb, uint8_t *req); int (*build_response_basis) (sft_t *sft, uint8_t *rsp); int (*prepare_response_tid) (const uint8_t *req, int *req_length); int (*send_msg_pre) (uint8_t *req, int req_length); ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length); int (*receive) (modbus_t *ctx, uint8_t *req); ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length); int (*check_integrity) (modbus_t *ctx, uint8_t *msg, const int msg_length); int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req, const uint8_t *rsp, int rsp_length); int (*connect) (modbus_t *ctx); void (*close) (modbus_t *ctx); int (*flush) (modbus_t *ctx); int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); void (*free) (modbus_t *ctx); } modbus_backend_t;
Описать её можно вот так:
class modbus_backend_t(Structure): _fields_ = [ ("backend_type", c_uint), ("header_length", c_uint), ("checksum_length", c_uint), ("max_adu_length", c_uint), ("set_slave", CFUNCTYPE(c_int, POINTER(modbus_t), c_int)), ("build_request_basis", CFUNCTYPE(c_int, POINTER(modbus_t), c_int, c_int, c_int, c_char_p)), ("build_response_basis", CFUNCTYPE(c_int, POINTER(sft_t), c_char_p)), ("prepare_response_tid", CFUNCTYPE(c_int, c_char_p, POINTER(c_int))), ("send_msg_pre", CFUNCTYPE(c_int, c_char_p, c_int)), ("send", CFUNCTYPE(c_ssize_t, POINTER(modbus_t), c_char_p, c_int)), ("receive", CFUNCTYPE(c_int, POINTER(modbus_t), c_char_p)), ("recv", CFUNCTYPE(c_ssize_t, POINTER(modbus_t), c_char_p, c_int)), ("check_integrity", CFUNCTYPE(c_int, POINTER(modbus_t), c_char_p, c_int)), ("pre_check_confirmation", CFUNCTYPE(c_int, POINTER(modbus_t), c_char_p, c_char_p, c_int)), ("connect", CFUNCTYPE(c_int, POINTER(modbus_t))), ("close", CFUNCTYPE(None, POINTER(modbus_t))), ("flush", CFUNCTYPE(c_int, POINTER(modbus_t))), ("select", CFUNCTYPE(c_int, POINTER(modbus_t), POINTER(fd_set), POINTER(timeval), c_int)), ("free", CFUNCTYPE(None, POINTER(modbus_t))) ]
Как видите указатель на функцию описывается при помощи CFUNCTYPE. Эта функция принимает на вход тип возвращаемого рузультата (или None в случае void) и все параметры. Указатели следует обернуть в POINTER.
Подготовительные работы окончены.
Теперь загрузим dll и выполним запрос.
from ctypes import * from pylibmodbus import * if __name__ == "__main__": mb = POINTER(modbus_t) r = create_unicode_buffer("", 32) libmodbus = CDLL("modbus.dll") mb = libmodbus.modbus_new_tcp("192.168.0.5".encode("ascii"),502) libmodbus.modbus_connect(mb) libmodbus.modbus_read_registers(mb,0,5,r) for o in r: print(ord(o)) libmodbus.modbus_close(mb) libmodbus.modbus_free(mb)
Загружаем dll при помощи CDLL(«path_to_dll»). После чего можно обращаться к её функциям.
Для объявления массива служит такая строка:
reg = c_ushort * 32
Не забываем добавлять метод .encode() для строковых параметров в python 3. Это связано с тем, что у него Unicode строки, а у dll — нет.
Код можно глянуть на GitHub.
Возможно когда-то он перерастет во что-то большее.
Иногда структуры ссылаются друг на друга. Интерпретатор законно ругается.
С этим можно справится так:
class modbus_backend_t(Structure): pass
А уже дальше описываем нужный тип данных.