Использование сторонних dll при помощи ctypes в python

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

В 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

А уже дальше описываем нужный тип данных.

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий