Интеграция C и Python: вызов нативных библиотек

Раздел: Расширения -> взаимодействие с C

Загрузка C библиотеки в Python: способы и примеры

При разработке на Python часто возникает необходимость использовать существующий код на C или C++. Это может быть связано с требованиями производительности, доступом к низкоуровневым системным вызовам или интеграцией с уже написанными библиотеками. Python предлагает несколько механизмов для загрузки и взаимодействия с разделяемыми библиотеками (DLL, SO, Dylib). В этой статье рассматриваются основные подходы, их преимущества и подводные камни.

Как загрузить разделяемую библиотеку на C и вызвать функцию в Python?

Наиболее распространённым и универсальным решением является модуль ctypes, входящий в стандартную библиотеку Python. Он позволяет динамически загружать библиотеки, определять типы аргументов и возвращаемых значений, а также вызывать функции без необходимости компиляции дополнительного кода.


import ctypes

# Загрузка библиотеки (путь может быть абсолютным или относительным)
lib = ctypes.CDLL("./libsample.so")  # Linux/Mac
# или lib = ctypes.CDLL("sample.dll")  # Windows

# Определение прототипа функции (тип возвращаемого значения и аргументов)
lib.my_function.restype = ctypes.c_int
lib.my_function.argtypes = [ctypes.c_int, ctypes.c_double]

# Вызов функции
result = lib.my_function(42, 3.14)
print(result)
    

Python list in c (использование списков python в c)

(пример вывода, зависящий от реализации функции)
    

Use python in c (использование python в коде c (встраивание))

Пояснение шагов:

  1. Импорт модуля ctypes.
  2. Создание объекта библиотеки с помощью CDLL (для Windows – windll или oledll). Путь может быть относительным или полным; можно использовать ctypes.util.find_library для поиска в системе.
  3. Указание типа возвращаемого значения через restype. Если функция возвращает void, используется None.
  4. Определение типов аргументов через argtypes – список типов из ctypes (c_int, c_double, c_char_p и т.д.). Это необязательно, но улучшает контроль и предотвращает ошибки.
  5. Вызов функции как обычного метода объекта библиотеки.

Типичные ошибки:

  • OSError: cannot open shared object file – библиотека не найдена. Проверить путь, переменную среды LD_LIBRARY_PATH (Linux) или PATH (Windows).
  • AttributeError: function not found – имя функции не совпадает с экспортированным (например, из-за name mangling в C++). Для C функций нужно использовать extern "C" или точное имя.
  • Ошибки при несовпадении типов: segfault или неверные результаты. Рекомендуется всегда задавать argtypes и restype.
  • Проблемы с кодировкой строк: ctypes по умолчанию передаёт строки как bytes. Если функция ожидает const char* в UTF-8, используйте c_char_p.

Этот метод подходит для большинства случаев, когда библиотека уже скомпилирована и не требует изменения кода на C.

Как использовать CFFI для более чистого интерфейса?

Модуль cffi (C Foreign Function Interface) предлагает альтернативный способ, при котором описание интерфейса задаётся в виде строки с декларациями на C. Это обеспечивает лучшую читаемость и более прямой контроль над типами.


from cffi import FFI

ffi = FFI()

# Описание функций и типов на C
ffi.cdef("""
    int my_function(int a, double b);
""")

# Загрузка библиотеки
lib = ffi.dlopen("./libsample.so")

# Вызов
result = lib.my_function(42, 3.14)
print(result)
    

Python c types (библиотека ctypes в python)

Проблемы: CFFI не входит в стандартную библиотеку, требуется установка (pip install cffi). При компиляции C кода (API mode) необходимо иметь компилятор. Однако для скомпилированных библиотек это не требуется.

Как скомпилировать C код в расширение Python с помощью Cython?

Cython позволяет писать код, похожий на Python, но компилируемый в C-расширение. Таким образом можно не только вызывать C-функции, но и создавать эффективные обёртки.


# example.pyx
cdef extern from "sample.h":
    int my_function(int a, double b)

def call_my_function(a, b):
    return my_function(a, b)
    

Python load c lib (загрузка c библиотеки в python)

Для сборки используется setup.py или pyximport. После компиляции получается обычный модуль Python.

Ошибки: требуется компилятор C и установленный Cython. Ошибки часто связаны с неверными путями к заголовочным файлам или несовместимостью типов.

Как создать нативные Python модули из C++ с pybind11?

Pybind11 – современная библиотека для создания биндингов C++ в Python. Она минимизирует шаблонный код и поддерживает множество возможностей C++.


#include <pybind11/pybind11.h>

int my_function(int a, double b) {
    return static_cast<int>(a + b);
}

PYBIND11_MODULE(example, m) {
    m.doc() = "example module";
    m.def("my_function", &my_function, "A function that adds");
}
    

Компиляция через CMake или setup.py с pybind11. На выходе – модуль .so/.pyd.

Проблемы: сложность сборки, зависимость от pybind11 и компилятора C++11/14. Ошибки линковки часто связаны с неверными флагами или версиями Python.

Как автоматически сгенерировать обертку ctypes из заголовочного файла?

Инструмент ctypesgen читает C-заголовок и генерирует Python модуль с использованием ctypes. Это удобно для больших библиотек.


ctypesgen -l sample sample.h -o sample_wrapper.py
    

После генерации можно импортировать готовый модуль.

Ошибки: некорректные объявления в заголовке (особенно макросы, сложные typedef). Иногда требуется ручная корректировка сгенерированного кода.

Расширенные примеры загрузки C библиотек

Передача структур и указателей через ctypes

Определим структуру на C и функцию, которая её заполняет.

Пример

// sample.h
typedef struct {
    int x;
    double y;
} Point;

void fill_point(Point* p, int x, double y);
Пример

import ctypes

class Point(ctypes.Structure):
    _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_double)]

lib = ctypes.CDLL("./libsample.so")
lib.fill_point.argtypes = [ctypes.POINTER(Point), ctypes.c_int, ctypes.c_double]
lib.fill_point.restype = None

p = Point()
lib.fill_point(ctypes.byref(p), 10, 20.5)
print(p.x, p.y)  # 10 20.5
10 20.5

Коллбэки (callback) из Python в C

В C определена функция, принимающая указатель на функцию.

Пример

// sample.h
typedef int (*callback_t)(int);
void set_callback(callback_t cb);
Пример

CALLBACK = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)

def my_callback(x):
    return x * 2

lib = ctypes.CDLL("./libsample.so")
lib.set_callback.argtypes = [CALLBACK]
lib.set_callback.restype = None

# Преобразуем Python функцию в C-совместимый указатель
cb = CALLBACK(my_callback)
lib.set_callback(cb)

Работа с динамическими строками (возврат malloc'ed string)

Пример

// Возвращает строку, которую нужно освободить free()
char* get_message();
void free_message(char* msg);
Пример

lib = ctypes.CDLL("./libsample.so")
lib.get_message.restype = ctypes.c_char_p
lib.free_message.argtypes = [ctypes.c_char_p]
lib.free_message.restype = None

msg_ptr = lib.get_message()  # возвращает указатель на C-строку
msg = ctypes.string_at(msg_ptr)  # копируем в Python bytes
print(msg)
lib.free_message(msg_ptr)  # освобождаем память
b'Hello from C!'

Использование ctypes.util.find_library

Пример

import ctypes.util

lib_path = ctypes.util.find_library("c")  # найдёт libc.so или msvcrt.dll
if lib_path:
    libc = ctypes.CDLL(lib_path)
    print(libc.rand())  # пример вызова случайного числа
1804289383 (зависит от RNG)

Загрузка библиотеки с зависимостями (RPATH, LD_LIBRARY_PATH)

Пример

import os
os.environ["LD_LIBRARY_PATH"] = "/opt/mylib/lib:" + os.environ.get("LD_LIBRARY_PATH", "")
# После этого загружать библиотеку стандартным способом

Пример CFFI с встроенным C-кодом

Пример

from cffi import FFI

ffi = FFI()
# Можно скомпилировать inline C код (требуется компилятор C)
ffi.set_source("_example",
    r"""
    int my_function(int a, double b) {
        return (int)(a + b);
    }
    """)
ffi.compile()
# После компиляции импортируем
from _example import lib
print(lib.my_function(1, 2.5))
3

Пример Cython с прямым вызовом

Пример

# example.pyx
cdef extern from "stdlib.h":
    int rand()

def get_random():
    return rand()

Сборка: cythonize -i example.pyx (требует Cython и компилятор).

загрузка C библиотеки в Python - comments

En
Python load c lib (python)