What is the correct callback signature for a function called ctypes in python?

I need to define a callback function in Python that will be called from a DLL.

BOOL setCallback (LONG nPort, void ( _stdcall *pFileRefDone) (DWORD nPort, DWORD nUser), DWORD nUser); 

I tried this code, which seems to work in Python 2.5, but it crashes with Python 2.7, and I assume that I did something wrong.

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

Where is the mistake?

Note. The platform only works with the 32-bit version (both Windows and Python). The DLL loads successfully, and other functions from the inside work fine when called from Python.

The full sample code that reproduces this is available at https://github.com/ssbarnea/pyHikvision - just run Video.py on py25 and py27.

+4
source share
2 answers

In the context of a Video class using a nested function:

 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 

This somewhat ugly but more natural option fails with TypeError during the callback:

 #XXX this won't work @WINFUNCTYPE(None, DWORD, DWORD) def cbFileRefDone(self, port, user_data): print "File indexed." 

To fix this, you can create a special function descriptor:

 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 

Using:

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

Additionally, MethodDescriptor stores references to C functions to prevent garbage collection.

+3
source

The CB_Func (cbFunc) parameter receives garbage collection immediately after the setCallback function. This object must be stored until a callback can be called ( 15.17.1.17. Callback functions , last paragraph). Assign it to a variable and save it. Here is my working example:

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); } 

Error 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,CALLBACK(cb_func),2) x.Fire() 

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

Change In addition, since CALLBACK is a function returning a function, it can be used as a decorator for Python callbacks, eliminating the problem of callbacks that go out of scope:

 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
source

All Articles