【问题标题】:What is the correct callback signature for a function called using ctypes in python?在 python 中使用 ctypes 调用的函数的正确回调签名是什么?
【发布时间】:2011-12-15 15:47:27
【问题描述】:

我必须在 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。

【问题讨论】:

  • 第一行给出的函数指针指向返回void的函数,而不是void*
  • 如果我的第一条评论不够清楚:这与WINFUNCTYPE 的返回类型声明相矛盾,即c_void_p
  • 使用None表示返回类型void。史蒂文是谁,顺便说一句? :)
  • 您无需在回调中使用ctypes.py_object(self) 来访问self。我们已经在您之前的问题中告诉过您。但是,您似乎打算按照自己的方式去做。这取决于你。对我来说,self 可以可靠地转换为 32 位 int 远非显而易见。另外,你的 Python 是 32 位还是 64 位?
  • cbFunc 在模块范围内吗?您的代码显然不完整,因为那里有一个self。我想知道cbFunc 是否是一个本地函数,当它的封闭函数结束时会超出范围。在模块范围内尝试使用cbFunc

标签: python callback ctypes stdcall


【解决方案1】:

您的 CB_Func(cbFunc) 参数在 setCallback 函数之后立即被垃圾收集。只要可以调用回调,该对象就必须持续存在(15.17.1.17. Callback functions,最后一段)。将它分配给一个变量并保留它。这是我的工作示例:

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() 

【讨论】:

    【解决方案2】:

    Video class 的上下文中使用嵌套函数:

    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 函数的引用以防止它们被垃圾回收。

    【讨论】:

    • 我不知道我可以将成员函数用作 C 回调函数,而不必关心 self 参数。这真的很酷。如果您不介意,我将从您的回复中删除无效的代码,以使其更易于阅读。
    • @sorin:第一个示例确实有效(如果您在__init__() 中添加self.__cbFileRefDone = [],则使用暗示:self__cbFileRefDone.append()。)。它只是使用嵌套函数而不是方法。不了解描述符的人更容易理解。此外,它允许使用本地到 open() 方法数据。缺点是它会在每次调用 open() 时创建一个新的函数对象(方法方法为每个对象创建一个回调,类型对)。
    • @J.F.Sebastian,由于装饰器只是一个返回函数的函数,你可以只使用回调类型作为装饰器。请参阅我的更新答案。
    • @Mark Tolonen:winfunctype() 不能用作方法函数的装饰器。我已经回滚了@sorin 对删除示例的答案的编辑。
    猜你喜欢
    • 1970-01-01
    • 2016-02-02
    • 2020-02-24
    • 1970-01-01
    相关资源
    最近更新 更多