Сообщить об ошибке.

Функции обратного вызова модуля ctypes в Python

Модуль ctypes позволяет создавать указатели вызываемых C-функций из вызываемых объектов Python. Иногда их называют функциями обратного вызова.

Во-первых, необходимо создать класс для функции обратного вызова. Класс знает соглашение о вызовах, тип возвращаемого значения, а также количество и типы аргументов, которые эта функция получит.

  • Фабричная функция ctypes.CFUNCTYPE() создает типы для функций обратного вызова, используя соглашение о вызовах cdecl.
  • В Windows фабричная функция ctypes.WINFUNCTYPE() создает типы для функций обратного вызова, используя соглашение о вызовах stdcall.

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

Представленный здесь пример, использует функцию qsort() стандартной библиотеки C, используемая для сортировки элементов с помощью функции обратного вызова. Функция qsort() будет использоваться для сортировки массива целых чисел:

>>> from ctypes import *
>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
# в Linux необходимо загрузить 
# C-библиотеку следующим образом:
# libc = CDLL("libc.so.6")
# смотрите раздел "Загрузка C-библиотек"
>>> qsort = libc.qsort
>>> qsort.restype = None

Функция стандартной C-библиотеки qsort() должна вызываться с указателем на данные для сортировки, количеством элементов в массиве с данными, размером одного элемента и указателем на функцию сравнения (обратный вызов). Затем обратный вызов будет вызываться с двумя указателями на элементы и должен возвращать: отрицательное целое число - если первый элемент меньше второго; ноль - если они равны и положительное целое число - в противном случае.

Таким образом, наша функция обратного вызова получает указатели на целые числа и должна возвращать целое число. Сначала мы создаем тип для функции обратного вызова:

CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))

Для начала вот простой обратный вызов, который показывает передаваемые значения:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func)  
# py_cmp_func 5 1
# py_cmp_func 33 99
# py_cmp_func 7 33
# py_cmp_func 5 7
# py_cmp_func 1 7

Теперь можно сравнить два элемента и вернуть полезный результат:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) 
# py_cmp_func 5 1
# py_cmp_func 33 99
# py_cmp_func 7 33
# py_cmp_func 1 7
# py_cmp_func 5 7

Теперь можно проверить, как массив отсортирован:

>>> for i in ia: print(i, end=" ")
...
1 5 7 33 99

Фабричные функции можно использовать как фабрики декораторов:

>>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
... def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func)
# py_cmp_func 5 1
# py_cmp_func 33 99
# py_cmp_func 7 33
# py_cmp_func 1 7
# py_cmp_func 5 7

Примечание. Необходимо убедится, что ссылки на объекты CFUNCTYPE() сохраняются, пока они используются из кода C. Модуль ctypes этого не делает, и если вы этого не сделаете, то они могут быть уничтожены сборщиком мусора, что приведет к сбою программы при выполнении обратного вызова.

Также обратите внимание, что если callback функция вызывается в потоке, созданном вне контроля Python (например, внешним кодом, вызывающим callback функцию), то модуль ctypes создает новый фиктивный поток Python при каждом вызове. Такое поведение является правильным для большинства целей. Это означает, что при различных callback вызовах, значения, хранящиеся в threading.local, не сохранятся даже если эти вызовы выполняются из одного и того же потока C.