Великий и могучий питон…
Правда бывают случаи когда в код на питоне хочется вставить код на си или еще чего. Толи дело в производительности, толи в не желании писать велосипед.
Не суть.
В 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
А уже дальше описываем нужный тип данных.







