Python对C回调的封装
我正在尝试创建一个Python的回调函数,这个函数需要在Windows环境下调用DLL中的C回调时被触发。请看下面的代码,以便理解我遇到的问题。
from ctypes import *
#---------qsort Callback-------------#
IntArray5 = c_int * 5
ia = IntArray5(5,1,7,33,99)
libc = cdll.msvcrt
qsort = libc.qsort
qsort.restype = None
CMPFUNC = CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int) )
test = 0
def py_cmp_func(a,b):
#print 'py_cmp_func:',a[0],b[0]
global test
test = 10000
return a[0]-b[0]
cmp_func = CMPFUNC(py_cmp_func)
qsort(ia, len(ia), sizeof(c_int), cmp_func)
print "global test=",test
for item in ia : print item
#----------Load DLL & Connect ------------#
gobiDLL = WinDLL("C:\LMS\QCWWAN2k.dll")
print 'Output of connect : ',gobiDLL.QCWWANConnect()
#----------SetByteTotalsCallback----------#
tx = POINTER(c_ulonglong)
rx = POINTER(c_ulonglong)
proto_callback = WINFUNCTYPE(c_void_p,tx,rx)
gtx = grx = 0 # Used to copy the response in the py_callback
def py_callback(t,r):
sleep(10)
print 'python callback ...'
print "tx=",t,"rx=",r
global gtx,grx
gtx = 5000 # gtx = t
grx = 2000 # grx = r
#return 0
callback = proto_callback(py_callback)
gobiDLL.SetByteTotalsCallback.restype = c_ulong
gobiDLL.SetByteTotalsCallback.argtypes = [proto_callback,c_byte]
print "SetByteTotalsCallback = ",gobiDLL.SetByteTotalsCallback(callback, c_byte(256))
print "gtx = ",gtx
print "grx = ",grx
这个DLL文档中描述了SetByteTotalsCallback()方法的原型和回调,如下所示。
原型:
ULONG QCWWANAPI2K SetSessionStateCallback( tFNSessionState pCallback );
回调:
void ByteTotalsCallback( ULONGLONG txTotalBytes, ULONGLONG rxTotalBytes );
输出:
>>>
global test= 10000
1
5
7
33
99
Output of connect : 0
SetByteTotalsCallback = 0
gtx = 0
grx = 0
>>>>
目前的问题是,整个程序都能正常调用,但Python的回调函数根本没有被调用。在调用gobiDLL.SetByteTotalsCallback(callback, c_byte(256))方法时,程序以0状态退出,但在这个过程中,Python中写的callback()方法并没有被触发。
你能指出什么可能帮助我调用Python的回调函数吗?另外一个示例qsort()方法能够很好地传递指向Python函数的指针。我现在搞不清楚问题的根本原因。
谢谢!
安东尼
1 个回答
你不能直接这么做。C/C++ 的函数不能直接访问 Python 的函数——那个函数原型可能是期待一个指向 C 的指针。Python 会传递一个指向它内部数据结构的指针,专门用于那个特定的函数。
这时候你需要创建一个 C 扩展来包装那个 DLL,并把它暴露给 Python。基本上,你要让 C 的回调函数去调用 Python 的回调函数,因为这是可以做到的。更清楚地说,你想要实现的是:
| This side is C land i.e. "real" addresses
|
Python objects --> C extension -- register callback with --> DLL
| |
in the python | Calls callback
| |
interpreter <-------------- Callback in C extension <-------
|
下面是一个非常简单的解释,讲的是如何从 C 调用 Python 函数。你需要用构建你 Python 发行版时使用的 MSVC(或者其他工具)来编译这段代码;用 depends.exe
来找出它链接的 msvcXX.dll
。
全局状态通常被认为是不好的,但为了简单起见,我用了这个:
static PyObject* pyfunc_event_handler = NULL;
static PyObject* pyfunc_event_args = NULL;
然后我添加了一个设置处理函数,这样设置回调就更简单了。不过,你不一定需要这样做,你只需要
static PyObject* set_event_handler(PyObject *self, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
下一行是关键——我允许传递两个 Python 对象(传给 PyArg_ParseTuple
的 O
参数)。一个对象是函数,另一个是它的参数。
if (PyArg_ParseTuple(args, "OO", &temp, &pyfunc_event_args)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be a function");
return NULL;
}
处理引用。Python 需要你这样做。
Py_XINCREF(temp); /* Add a reference to new func */
Py_XDECREF(pyfunc_event_handler); /* Dispose of previous callback */
pyfunc_event_handler = temp; /* Remember new callback */
/* Boilerplate to return "None" */
Py_INCREF(Py_None);
result = Py_None;
}
return result;
}
然后你可以在其他地方这样调用:
PyObject* arglist = Py_BuildValue("(O)", pyfunc_event_args);
pyobjresult = PyObject_CallObject(pyfunc_event_handler, arglist);
Py_DECREF(arglist);
别忘了 DECREF
,你需要让 Python 垃圾回收 arglist。
从 Python 使用这个非常简单:
set_event_handler(func, some_tuple)
其中 func 的参数要匹配,像这样:
def func(obj):
/* handle obj */
你可能想了解的内容:
- LoadLibrary(从 C 加载 DLL)。
- GetProcAddress(查找要调用的函数)。
- 用 C 或 C++ 扩展 Python,来自 Python 文档。