Создание функции 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".

Другие вопросы по тегам