Rex Low
Rex Low

Reputation: 2167

Ctypes callback function with arguments

Not sure what I am asking here is a proper way to do it (can't find any similar question), but here goes.

I need to initiate a callback function with custom arguments alongside with the ctypes arguments.

The Function

def initMessageCallback(myData):

    callback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_long) # return type and output parameter

    lib.SetMessageCallback.restype = ctypes.c_bool
    lib.SetMessageCallback(callback(callbackFunc))

Callback Function

I can access the parameter returned by SetMessageCallback but how can I pass myData during initMessageCallback so that I can access it inside callbackFunc without making it a global variable?

def callbackFunc(ID):
    # need to access myData here

Upvotes: 1

Views: 2160

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177745

There's a couple ways to do this. Your callback could be a method in a class and can access the instance data of the instantiated class, or you can just attach the data to the callback itself. Here's an example of the latter and some sample callback code:

test.c

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

typedef int (*CALLBACK)(int);

CALLBACK g_callback;

API void set_callback(CALLBACK cb)
{
    g_callback = cb;
}

API int do_callback()
{
    int sum = 0;
    for(int i = 0; i < 5; ++i) // Call callback with 0,1,2,3,4 parameters.
        if(g_callback)
            sum += g_callback(i); // Collect and sum the callback return values.
    return sum;
}

test.py

from ctypes import *

CALLBACK = CFUNCTYPE(c_int,c_int)

dll = CDLL('test')
dll.set_callback.argtypes = CALLBACK,
dll.set_callback.restype = c_int
dll.do_callback.argtypes = ()
dll.do_callback.restype = c_int

@CALLBACK
def my_callback(id):
    return id + my_callback.my_data # Use the extra callback data.

def init_callback(my_data):
    my_callback.my_data = my_data  # Simply attach the data as a variable of the callback.
    dll.set_callback(my_callback)

init_callback(0)
print(dll.do_callback()) # Expect (0+0)+(1+0)+(2+0)+(3+0)+(4+0) = 10
init_callback(1)
print(dll.do_callback()) # Expect (0+1)+(1+1)+(2+1)+(3+1)+(4+1) = 15

Output

10
15

A note about your sample code. callback(callbackFunc) is created and passed to lib.SetMessageCallback() but since no reference exists after that line executes the callback is destroyed. That can crash your system when you try to use the callback. Using a decorator (@CALLBACK) against the callback function like I've done above is the equivalent of my_callback = CALLBACK(my_callback). The function name is at the global level and won't go out of scope. So keep that in mind when generating callbacks. Store them in a variable and make sure it stays in scope until you're done with it.

Upvotes: 1

Related Questions