Создание функции python, вызываемой из C, с входным параметром, имеющим семантику *output*
Вариант использования следующий:
- Учитывая (фиксированная, не изменяемая) DLL, реализованная в C
- Требуется: оболочка к этой DLL, реализованная в python (выбранный метод: ctypes)
Некоторым функциям в DLL нужны примитивы синхронизации. Чтобы достичь максимальной гибкости, разработчики DLL полностью полагаются на предоставляемые клиентом обратные вызовы. Точнее, эта DLL должна иметь:
- функция обратного вызова для создания объекта синхронизации
- функции обратного вызова для получения / снятия блокировки объекта синхронизации
- и одна функция обратного вызова для уничтожения объекта синхронизации
Поскольку с точки зрения DLL объект синхронизации является непрозрачным, он будет представлен void *
юридическое лицо. Например, если одна из функций DLL хочет получить блокировку, она должна выполнить:
void* mutex;
/* get the mutex object via the create_mutex callback */
create_mutex(&mutex);
/* acquire a lock */
lock_mutex(mutex);
... etc
Видно, что ответный звонок create_mutex
входной параметр имеет семантику вывода. Это достигается с помощью void **
подпись.
Этот обратный вызов (и остальные три) должен быть реализован в Python. Я потерпел неудачу:-) Для простоты, давайте сосредоточимся только на создании обратного вызова, а также для простоты, пусть непрозрачный объект будет int
,
Игрушечная DLL, которая эмулирует использование обратных вызовов, выглядит следующим образом (ct_test.c):
#include <stdio.h>
#include <stdlib.h>
typedef int (* callback_t)(int**);
callback_t func;
int* global_i_p = NULL;
int mock_callback(int** ipp)
{
int* dynamic_int_p = (int *) malloc(sizeof(int));
/* dynamic int value from C */
*dynamic_int_p = 2;
*ipp = dynamic_int_p;
return 0;
}
void set_py_callback(callback_t f)
{
func = f;
}
void set_c_callback()
{
func = mock_callback;
}
void test_callback(void)
{
printf("global_i_p before: %p\n", global_i_p);
func(&global_i_p);
printf("global_i_p after: %p, pointed value:%d\n", global_i_p, *global_i_p);
/* to be nice */
if (func == mock_callback)
free(global_i_p);
}
Код Python, который хотел бы обеспечить обратный вызов и использовать DLL, выглядит следующим образом:
from ctypes import *
lib = CDLL("ct_test.so")
# "dynamic" int value from python
int = c_int(1)
int_p = pointer(int)
def pyfunc(p_p_i):
p_p_i.contents = int_p
# create callback type and instance
CALLBACK = CFUNCTYPE(c_int, POINTER (POINTER(c_int)))
c_pyfunc = CALLBACK(pyfunc)
# functions from .so
set_py_callback = lib.set_py_callback
set_c_callback = lib.set_c_callback
test_callback = lib.test_callback
# set one of the callbacks
set_py_callback(c_pyfunc)
#set_c_callback()
# test it
test_callback()
При использовании in-DLL предоставляется обратный вызов (устанавливается через set_c_callback()
), это работает как ожидалось:
~/dev/test$ python ct_test.py
global_i_p before: (nil)
global_i_p after: 0x97eb008, pointed value:2
Однако, в другом случае - с обратным вызовом python - происходит сбой:
~/dev/test$ python ct_test.py
global_i_p before: (nil)
Traceback (most recent call last):
File "/home/packages/python/2.5/python2.5-2.5.2/Modules/_ctypes/callbacks.c", line 284, in 'converting callback result'
TypeError: an integer is required
Exception in <function pyfunc at 0xa14079c> ignored
Segmentation fault
Где я не прав?
2 ответа
Ошибка происходит из-за неправильной обработки указателей в вашем обратном вызове Python. У вас больше уровней косвенного обращения с указателями, чем строго по необходимости, что, вероятно, является источником вашей путаницы. В обратном вызове Python вы устанавливаете p_p_i.contents
, но это только меняет то, на что указывает объект Python ctypes, а не базовый указатель. Для этого выполните разыменование указателя через синтаксис доступа к массиву. Дистиллированный пример:
ip = ctypes.POINTER(ctypes.c_int)()
i = ctypes.c_int(99)
# Wrong way
ipp = ctypes.pointer(ip)
ipp.contents = ctypes.pointer(i)
print bool(ip) # False --> still NULL
# Right way
ipp = ctypes.pointer(ip)
ipp[0] = ctypes.pointer(i)
print ip[0] # 99 --> success!
Ошибка типа связана с несовместимостью типов, как описано в ответе Питера Хансена.
Вы, кажется, неправильно определяете тип возвращаемого значения. Похоже, ваш обратный вызов C возвращает int, в то время как тот Python, который вы объявляете как return c_int, но ничего не возвращает явно (таким образом, фактически возвращает None). Если вы вернете 0, это может прекратить сбой. Вы должны сделать это или изменить подпись обратного вызова на CFUNCTYPE(None, ...etc)
в любом случае.
Кроме того, хотя здесь проблема не актуальна, вы скрываете встроенное имя int. Это может привести к проблемам позже.
Отредактировано: для правильного обращения к типу возврата C как к "int", а не "void".