Какова правильная сигнатура обратного вызова для функции, вызываемой с использованием ctypes в python? - PullRequest
4 голосов
/ 26 октября 2011

Мне нужно определить функцию обратного вызова в 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 - просто запустите Video.py под py25 и py27.

Ответы [ 2 ]

3 голосов
/ 28 октября 2011

В контексте Класс видео с использованием вложенной функции:

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, чтобы предотвратить их сборку мусора.

3 голосов
/ 27 октября 2011

Ваш параметр CB_Func (cbFunc) получает сборщик мусора сразу после функции setCallback. Этот объект должен сохраняться до тех пор, пока может быть вызван обратный вызов ( 15.17.1.17. Функции обратного вызова , последний абзац). Присвойте его переменной и сохраните. Вот мой рабочий пример:

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

Passing Script

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