使用ctypes在Python中调用函数的正确回调签名是什么?
我需要在Python中定义一个回调函数,这个函数会从一个DLL(动态链接库)中被调用。
BOOL setCallback (LONG nPort, void ( _stdcall *pFileRefDone) (DWORD nPort, DWORD nUser), DWORD nUser);
我试过这个代码,在Python 2.5中似乎可以正常工作,但在Python 2.7中却崩溃了,我想我可能做错了什么。
import ctypes, ctypes.wintypes
from ctypes.wintypes import DWORD
def cbFunc(port, user_data):
print "Hurrah!"
CB_Func = ctypes.WINFUNCTYPE(None, DWORD, DWORD)
mydll.setCallback(ctypes.c_long(0), CB_Func(cbFunc), DWORD(0))
错误出在哪里呢?
注意:这个平台只支持32位(包括Windows和Python)。DLL加载成功,而且从Python调用里面的其他函数也都正常工作。
一个完整的示例代码可以在这个链接找到:https://github.com/ssbarnea/pyHikvision - 只需在py25和py27下运行Video.py。
2 个回答
4
你的 CB_Func(cbFunc) 参数在 setCallback 函数执行完后就会被垃圾回收。这意味着这个对象必须在回调函数可以被调用的整个过程中保持存在。为了做到这一点,你需要把它赋值给一个变量并保持它的存在。下面是我一个能正常工作的例子:
DLL
typedef unsigned int DWORD;
typedef long LONG;
typedef int BOOL;
#define TRUE 1
#define FALSE 0
typedef void (__stdcall *CALLBACK)(DWORD,DWORD);
CALLBACK g_callback = 0;
DWORD g_port = 0;
DWORD g_user = 0;
BOOL __declspec(dllexport) setCallback (LONG nPort, CALLBACK callback, DWORD nUser)
{
g_callback = callback;
g_port = nPort;
g_user = nUser;
return TRUE;
}
void __declspec(dllexport) Fire()
{
if(g_callback)
g_callback(g_port,g_user);
}
失败的脚本
from ctypes import *
def cb_func(port,user):
print port,user
x = CDLL('x')
CALLBACK = WINFUNCTYPE(None,c_uint,c_uint)
#cbfunc = CALLBACK(cb_func)
x.setCallback(1,CALLBACK(cb_func),2)
x.Fire()
成功的脚本
from ctypes import *
def cb_func(port,user):
print port,user
x = CDLL('x')
CALLBACK = WINFUNCTYPE(None,c_uint,c_uint)
cbfunc = CALLBACK(cb_func)
x.setCallback(1,cbfunc,2)
x.Fire()
编辑:另外,由于 CALLBACK
是一个返回函数的函数,它可以用作 Python 回调的装饰器,这样就解决了回调函数超出作用域的问题:
from ctypes import *
CALLBACK = WINFUNCTYPE(None,c_uint,c_uint)
@CALLBACK
def cb_func(port,user):
print port,user
x = CDLL('x')
x.setCallback(1,cb_func,2)
x.Fire()
3
在使用Video类的上下文中,有一个嵌套函数:
class Video(object):
def __init__(self, ...):
self.__cbFileRefDone = []
def open(self, filename):
@WINFUNCTYPE(None, DWORD, DWORD)
def cbFileRefDone(port, user_data):
print "File indexed.", filename
self.__cbFileRefDone.append(cbFileRefDone) # save reference
if not self.hsdk.PlayM4_SetFileRefCallBack(
c_long(self.port), cbFileRefDone, DWORD(0)):
logging.error("Unable to set callback for indexing")
return False
这个写法有点丑,但如果用更自然的方式写,回调时会出现类型错误(TypeError):
#XXX this won't work
@WINFUNCTYPE(None, DWORD, DWORD)
def cbFileRefDone(self, port, user_data):
print "File indexed."
为了解决这个问题,可以创建一个特殊的函数描述符:
def method(prototype):
class MethodDescriptor(object):
def __init__(self, func):
self.func = func
self.bound_funcs = {} # hold on to references to prevent gc
def __get__(self, obj, type=None):
assert obj is not None # always require an instance
try: return self.bound_funcs[obj,type]
except KeyError:
ret = self.bound_funcs[obj,type] = prototype(
self.func.__get__(obj, type))
return ret
return MethodDescriptor
用法:
class Video(object):
@method(WINFUNCTYPE(None, DWORD, DWORD))
def cbFileRefDone(self, port, user_data):
print "File indexed."
def open(self, filename):
# ...
self.hsdk.PlayM4_SetFileRefCallBack(
c_long(self.port), self.cbFileRefDone, DWORD(0))
另外,MethodDescriptor会保存对C语言函数的引用,以防止它们被垃圾回收。