构建可从C调用的Python函数,输入参数具有*输出*语义
这个案例是这样的:
- 有一个用C语言写的DLL(动态链接库),这个DLL是固定的,不能更改
- 想要用Python来封装这个DLL(选择的方法是: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);
}
想要提供回调并使用这个DLL的Python代码如下:
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()
当使用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!
类型错误是因为类型不兼容,正如Peter Hansen的回答中所描述的那样。
你似乎在定义返回类型的时候搞错了。看起来你的C语言回调函数返回的是一个整数(int),而你在Python中声明的返回类型是c_int,但实际上没有明确返回任何东西(所以实际上返回的是None)。如果你加上“return 0”,可能就不会崩溃了。你应该这样做,或者把回调函数的定义改成CFUNCTYPE(None, ...etc)
。
另外,虽然现在不是问题,但你把“int”这个内置名称给覆盖了。这可能会在以后引发问题。
编辑:正确地将C语言的返回类型称为“int”,而不是“void”。