Reputation: 1
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 ?
Upvotes: 0
Views: 592
Reputation: 41137
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 cts >>> >>> Callback = cts.CFUNCTYPE(None) # Generic callback type - might be a cts.c_void_p as well >>> CallbackArray = Callback * 5 >>> >>> Callback1 = cts.CFUNCTYPE(None, cts.c_ulong, cts.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] = cts.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 = cts.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.
Upvotes: 0
Reputation: 177971
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
Upvotes: 0