Python Ctypes register callback functions
Question: I ran into something very strange using Python and ctypes. I'm using Python
3.4.3. First, some background into the project:

I have compiled a custom dll from C code. I'm using ctypes to interface with
the dll. The C library is interfacing with some custom hardware. Sometimes the
hardware generates an interrupt and passes it along to the C library on the
computer. In the C API, there is a function with the prototype `void
register_callback(int addr, void (*callback)(void))`. I have an array of
callback function pointers, which are initialized to NULL. When this function
is called, the callback function pointer at index addr is set to callback,
like this: `callbacks[addr] = callback;`.

When the user programs in Python, they instantiate objects from classes that
model different hardware parts (such as a button or an RGB LED). They can then
write a custom callback function and call `button.register_callback(func)`
(assuming they have a Button object named button, of course), which calls the
register_callback function in the C library. Now, when the button is pressed
and the interrupt is generated, the C library will call the appropriate
callback function (i.e. `callbacks[addr]();`).

Now, the weirdness:

In Python, my first attempt at the register_callback method in Python looked
like this:

    
    
    class Obj:
        def __init__(self, name):
            # Initialize stuff
    
        def register_callback(self, func):
            CB_T = ctypes.CFUNCTYPE(None)
            cb_ptr = CB_T(func)
            host_api.register_callback(self.addr, cb_ptr) # host_api is the loaded dll
    

And in main:

    
    
    def cb1():
        print("cb1")
    
    def cb2():
        print("cb2")
    
    def main(argv):
        # Initialization stuff
        # Now create the objects and register the callbacks:
        obj = Obj_module.Obj()
        obj2 = Obj_module.Obj()
        obj.register_callback(cb1)
        obj2.register_callback(cb2)
    
        while True:
            pass
    

When I ran this, only "cb2" was being printed, regardless of which button I
was pressing. The REALLY weird thing is that when I switched the order in
which I registered the callbacks :

    
    
        obj2.register_callback(cb2)
        obj.register_callback(cb1)
    

only "cb1" was being printed, regardless of the button I pressed! In the C
library, I verified (by printf) that different callback function pointers were
being set and called, depending on the button, but that the same function
pointer was being passed to the C register_callback function.

I was able to fix the problem by adding a line to the register_callback
method:

    
    
    def register_callback(self, func):
        CB_T = ctypes.CFUNCTYPE(None)
        cb_ptr = CB_T(func)
        (ctypes.cast(cb_ptr, ctypes.POINTER(ctypes.c_int)))
        host_api.register_callback(self.addr, cb_ptr)
    

Apparently, converting cb_ptr to a ctypes POINTER fixed the problem -
different function pointers were being passed in, and I successfully saw "cb1"
or "cb2" printed, depending on the button that I pressed.

My question is, WHY? Why was the same function pointer being passed in the
original code, why was it changing depending on the order that I registered
the callbacks, and why does converting cb_ptr to a ctypes POINTER ensure that
the function pointers are different?

I'm kind of a beginner at Python, but I'm much more experienced at C. Thanks
in advance for your responses.


Answer: Your `cb_ptr` is being garbage collected. From the
[documentation](https://docs.python.org/2/library/ctypes.html#callback-
functions):

> Make sure you keep references to CFUNCTYPE() objects as long as they are
> used from C code. ctypes doesn’t, and if you don’t, they may be garbage
> collected, crashing your program when a callback is made.

In this code sample if the line `ptrs.append(cb_ptr)` is commented out the
location of `cb_ptr` is the same for both `Obj` instances (on my computer).
Uncommenting the line results in two memory locations.

    
    
    import ctypes
    
    ptrs = []
    
    class Obj:
        def __init__(self):
            pass
    
        def register_callback(self, func):
            CB_T = ctypes.CFUNCTYPE(None)
            cb_ptr = CB_T(func)
            ptrs.append(cb_ptr)
            print(cb_ptr)
    
    def cb1(): print("cb1")
    
    def cb2(): print("cb2")
    
    def main(argv):
        obj = Obj()
        obj2 = Obj()
        obj.register_callback(cb1)
        obj2.register_callback(cb2)
    
    main(None)
    

