Работа с динамическими библиотеками DLL/so в языке Python для взаимодействия с кодом на C и C++
Основные подходы к работе с динамическими библиотеками в Python
Как загрузить существующую динамическую библиотеку и вызвать функцию из Python?
Самый простой и встроенный способ — использование модуля ctypes. Он позволяет загружать разделяемые библиотеки (.dll в Windows, .so в Linux) и вызывать экспортированные функции, автоматически преобразуя типы Python в типы C.
Пример загрузки библиотеки math в Linux (libm.so) и вызова функции sqrt:
from ctypes import CDLL
lib = CDLL('libm.so.6') # или './mylib.dll' на Windows
result = lib.sqrt(25.0)
print(result) # выведет приблизительно 5.0Python c import (импорт c библиотек в python)
Пояснение:
- CDLL загружает библиотеку и считает, что функции используют стандартный вызов C (cdecl). Для Windows с соглашением stdcall используется WinDLL.
- Тип возвращаемого значения по умолчанию — int. Для функции с double нужно явно задать: lib.sqrt.restype = c_double.
- Аргументы также можно уточнять: lib.sqrt.argtypes = [c_double].
Типичные ошибки:
- OSError: [WinError 126] — библиотека не найдена. Проверьте путь, разрядность библиотеки (32/64 бит должна совпадать с Python).
- Segmentation fault при неправильном задании типов аргументов или возвращаемого значения. Всегда указывайте argtypes и restype.
- Проблемы с кодировкой строк: функции, ожидающие const char*, требуют преобразования через c_char_p. Если строка должна изменяться, используйте create_string_buffer.
Как безопасно и с поддержкой C99 вызывать функции из динамической библиотеки?
Модуль cffi (C Foreign Function Interface) предлагает два подхода: быструю загрузку существующих библиотек (ABI) и генерацию C-кода (API). Он лучше контролирует типы и не требует ручного задания argtypes.
Пример использования cffi для вызова функции sqrt из libm:
from cffi import FFI
ffi = FFI()
ffi.cdef('double sqrt(double x);')
lib = ffi.dlopen('libm.so.6')
print(lib.sqrt(25.0))динамические библиотеки python (динамические библиотеки (dll/so) в python)
Пояснение:
- Метод cdef описывает заголовки функций на языке C.
- dlopen загружает библиотеку.
- Типы контролируются автоматически, ошибки преобразования практически исключены.
Ошибки:
- ImportError: no module named '_cffi_backend' — не установлен CFFI. Установите через pip.
- При использовании API-режима (из C-файлов) требуется компилятор; ABI-режим обходится без него.
- Библиотеки с нестандартными соглашениями вызова могут потребовать ручного указания ffi.dlopen(..., flags=...).
Как создать собственное расширение Python на C++ и получить динамическую библиотеку (.pyd / .so)?
Библиотека pybind11 упрощает создание расширений из C++. Она генерирует код, который компилируется в разделяемую библиотеку, импортируемую как обычный модуль Python.
Пример: простой модуль с функцией, возвращающей сумму двух чисел:
#include <pybind11/pybind11.h>
int add(int a, int b) {
return a + b;
}
PYBIND11_MODULE(myadd, m) {
m.doc() = "My add module";
m.def("add", &add, "Adds two integers");
}
Файл setup.py для сборки:
from setuptools import setup
from pybind11.setup_helpers import Pybind11Extension
ext_module = Pybind11Extension('myadd', ['myadd.cpp'])
setup(name='myadd', ext_modules=[ext_module])
Сборка и установка:
python setup.py build_ext --inplace
После этого модуль можно импортировать:
import myadd
print(myadd.add(2, 3)) # 5
Типичные проблемы:
- Ошибки линковки при отсутствии pybind11 или неправильном пути к заголовкам. Установите pybind11 через pip.
- Конфликт соглашений вызова (cdecl vs stdcall) актуален только для Windows; pybind11 использует __declspec(dllexport) автоматически.
- Для работы с многопоточностью в функциях C++ нужно освобождать GIL (pybind11 предоставляет макросы).
Как ускорить код Python, компилируя его в динамическую библиотеку с помощью Cython?
Cython транслирует Python-подобный код в C, который затем компилируется в разделяемую библиотеку. Это подход для случаев, когда нужно получить высокую производительность без написания чистого C.
Пример: файл fast_loop.pyx с функцией, вычисляющей сумму квадратов:
def sum_squares(int n):
cdef int i, s = 0
for i in range(n):
s += i * i
return s
Файл setup.py:
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize('fast_loop.pyx'))
Сборка:
python setup.py build_ext --inplace
Использование:
import fast_loop
print(fast_loop.sum_squares(1000))
Ошибки при работе с Cython:
- Cython.Compiler.Errors.CompileError — синтаксические ошибки в .pyx файле. Проверьте типы и отступы.
- Неправильное объявление типов может привести к снижению производительности или ошибкам сегментации.
- Для использования внешних библиотек C внутри Cython нужно явно объявлять их в cdef extern from.
Расширенные примеры работы с динамическими библиотеками
Пример 1: Передача массивов в функцию C через ctypes (с обработкой numpy)
Часто требуется передать большой массив чисел в C-функцию. Используем numpy.ctypes для получения указателя на данные.
import numpy as np
from ctypes import CDLL, c_double, c_int
lib = CDLL('./mystuff.so')
lib.process_array.argtypes = [POINTER(c_double), c_int]
lib.process_array.restype = None
data = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64)
ptr = data.ctypes.data_as(POINTER(c_double))
lib.process_array(ptr, len(data))
print(data) # массив будет изменён in-place
Результат (вывод изменённого массива):
[2.0, 4.0, 6.0, 8.0]
Пояснение: C-функция process_array удваивает каждый элемент. Использование POINTER(c_double) позволяет передать указатель на буфер numpy без копирования.
Пример 2: Регистрация callback-функции Python в C-библиотеке (ctypes + ctypes.CFUNCTYPE)
Допустим, C-библиотека ожидает указатель на функцию-обработчик. Python может передать свою функцию с помощью CFUNCTYPE.
from ctypes import CDLL, CFUNCTYPE, c_int
# Предположим, в библиотеке определена функция:
# void register_callback(void (*f)(int));
CALLBACK = CFUNCTYPE(None, c_int)
lib = CDLL('./callbacks.dll')
lib.register_callback.argtypes = [CALLBACK]
lib.register_callback.restype = None
def my_callback(value):
print(f"Callback called with {value}")
callback_func = CALLBACK(my_callback)
lib.register_callback(callback_func)
# Далее C-код может вызывать my_callback
Результат (при вызове из C):
Callback called with 42
Важно: объект callback_func должен существовать, пока библиотека может его использовать. Иначе Python garbage collector удалит его, что приведёт к краху.
Пример 3: Создание класса C++ с методами и свойствами через pybind11
Покажем, как обернуть простой класс Counter с конструктором, методом и свойством.
#include <pybind11/pybind11.h>
class Counter {
public:
Counter(int start) : count(start) {}
void increment(int step = 1) { count += step; }
int get_count() const { return count; }
private:
int count;
};
namespace py = pybind11;
PYBIND11_MODULE(counter, m) {
py::class_<Counter>(m, "Counter")
.def(py::init<int>())
.def("increment", &Counter::increment, py::arg("step") = 1)
.def("get_count", &Counter::get_count)
.def_property_readonly("count", &Counter::get_count);
}
Сборка аналогично примеру выше. Использование в Python:
import counter
c = counter.Counter(10)
c.increment(5)
print(c.count) # 15
Результат:
15
Обратите внимание: def_property_readonly создаёт атрибут только для чтения. Для изменяемых свойств используется def_property.
Пример 4: Оптимизация цикла с помощью Cython и внешней математической библиотеки
Допустим, нужно быстро вычислить сумму синусов большого массива. Используем Cython с вызовом sin из libm.
# fast_sum.pyx
cdef extern from "math.h" nogil:
double sin(double x)
def fast_sin_sum(double[:] arr):
cdef Py_ssize_t i
cdef double s = 0.0
for i in range(arr.shape[0]):
s += sin(arr[i])
return s
Для компиляции потребуется указать библиотеку math при линковке (в setup.py):
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize(
'fast_sum.pyx',
libraries=['m'] # линковка с libm
))
Тест:
import numpy as np
import fast_sum
data = np.linspace(0, np.pi, 1000000, dtype=np.float64)
print(fast_sum.fast_sin_sum(data))
Результат (приблизительное значение):
1999999.9999999998
Cython автоматически использует быстрый доступ к памяти double[:] и освобождает GIL (nogil), что даёт почти скорость C.