0

This is my problem, I have a legacy library (.so) written in C with APIs like this:

typedef void (*CALLBACK)( void);
typedef CALLBACK CALLBACK_TBL[ 5 ];
void init(CALLBACK_TBL callbackTbl)
{
        T_MYCALLBACK *myCallback1 = (T_MYCALLBACK *)(callbackTbl[0]);
        if (myCallback1 )
        {
            myCallback1(2,3);
        }
}

Of course, because it is a legacy library, I cannot change the API signature.

Now From Python, I am trying to call init with callback defined into python:

CallbackType1 = ctypes.CFUNCTYPE(None, c_ulong, c_ulong)
CallbackType2 = ctypes.CFUNCTYPE(None, c_ubyte, c_ubyte)
...
CallbackType5 = ctypes.CFUNCTYPE(None, c_int32, c_int32)


def callback1(long1, long2):
    print("callback1")

def callback2(bool1, bool2):
    print("callback2")
...
def callback5(int1, int2):
    print("callback5")

But I am not able to understand how am I supposed to make such an array of callbacks:

_callback1 = CallbackType1(callback1)
_callback2 = CallbackType1(callback2)
...
_callback5 = CallbackType1(callback5)

lib = CDLL("lib.so")
lib.init(....) ?????

Does somebody have an idea ?

  • What's *T\_MYCALLBACK*? Why is an entry in the *CALLBACK* array converted to a *T\_MYCALLBACK*? What are those *Python* functions with different *int* type arguments? Seems like some *C* parts are missing (if each of the 5 signatures differs). Then also I think *CALLBACK* is useless, it should have been *void\**. – CristiFati Mar 28 '20 at 20:07
  • typedef CALLBACK is a generic Callback definition. For T_MYCALLBACK, it is just an example of how a generic callback signature might be cast to a specific signature. Of course some C parts are missing... because my need is on the python side where i need to pass an array of callback as one argument – Michael M. Mar 28 '20 at 20:20

2 Answers2

0

A working minimal example would be nice. I've created one below but if it doesn't work for you update your question with a similar DLL example that matches your situation:

test.cpp

typedef void (*CALLBACK)(); // generic
typedef void (*CALLBACK1)(long,long);
typedef void (*CALLBACK2)(bool,bool);
typedef void (*CALLBACK3)(int,int,int);

typedef CALLBACK CALLBACK_TBL[3];

extern "C" __declspec(dllexport)
void init(CALLBACK_TBL callbackTbl)
{
    ((CALLBACK1)callbackTbl[0])(1L,2L);
    ((CALLBACK2)callbackTbl[1])(true,false);
    ((CALLBACK3)callbackTbl[2])(1,2,3);
}

test.py

from ctypes import *

CALLBACK1 = CFUNCTYPE(None,c_long,c_long)
CALLBACK2 = CFUNCTYPE(None,c_bool,c_bool)
CALLBACK3 = CFUNCTYPE(None,c_int,c_int,c_int)

class CALLBACK_TBL(Structure):
    _fields_ = [('cb1',CALLBACK1),
                ('cb2',CALLBACK2),
                ('cb3',CALLBACK3)]

@CALLBACK1
def callback1(a,b):
    print('callback1',a,b)

@CALLBACK2
def callback2(a,b):
    print('callback2',a,b)

@CALLBACK3
def callback3(a,b,c):
    print('callback3',a,b,c)

cbt = CALLBACK_TBL(callback1,callback2,callback3)

dll = CDLL('./test')
dll.init.argtypes = CALLBACK_TBL,
dll.init.restype = None

dll.init(cbt)

Output:

callback1 1 2
callback2 True False
callback3 1 2 3
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thank you guys for trying to help me.... Your answer almost fix my problem but i have got the following error with python2.6: TypeError: incompatible types, CFunctionType instance instead of CFunctionType instance and this error seems to be related to the generic CFUNCTYPE(None) – Michael M. Mar 29 '20 at 13:47
  • @MichaelM. That's due to your varying signatures but a generic callback array. Ideally the original library would use a structure of callbacks instead of a generic array with casting, but on the Python side if you carefully declare a structure with the right signatures it will still work without awkward casts, since the binary representation of the structure will align with the array. I'll updated my example. Be sure to read [What to do when someone answers my question](https://stackoverflow/help/someone-answers) as well. – Mark Tolonen Mar 29 '20 at 19:52
0

Listing [Python 3.Docs]: ctypes - A foreign function library for Python.

The simplest thing is to take the C code as it is (without thinking too much about it) and convert it to Python:

>>> import ctypes as ct
>>>
>>> Callback = ct.CFUNCTYPE(None)  # Generic callback type - might be a ct.c_void_p as well
>>> CallbackArray = Callback * 5
>>>
>>> Callback1 = ct.CFUNCTYPE(None, ct.c_ulong, ct.c_ulong)
>>> def func1(long1, long2): pass
...
>>> callback1 = Callback1(func1)
>>>
>>> # The rest of Callback# func# and callback#
>>>
>>> callback_array = CallbackArray()
>>> callback_array
<__main__.CFunctionType_Array_5 object at 0x000001517253CA48>
>>> callback_array[0]
<CFunctionType object at 0x000001517240BBA8>
>>>
>>> callback_array[0] = ct.cast(callback1, Callback)  # !!! Conversion needed !!!
>>> callback_array[0]
<CFunctionType object at 0x000001517240BE18>

>>> # Same thing for callback_array[1] .. callback_array[4]

And the rest of the code (I didn't paste it in the console as it wouldn't work):

lib = ct.CDLL("./lib.so")

lib.init.argtypes = [CallbackArray]  # Check the URL at the end

lib.init(callback_array)

Always define argtypes (and restype) for functions imported from .dlls. Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for more details.

CristiFati
  • 38,250
  • 9
  • 50
  • 87