В Python как передать массив обратного вызова в качестве аргументов функции C, используя ctypes? - PullRequest
0 голосов
/ 28 марта 2020

Это моя проблема, у меня есть устаревшая библиотека (.so), написанная на C с такими API-интерфейсами:

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

Конечно, поскольку это устаревшая библиотека, я не могу изменить Подпись API.

Теперь с Python я пытаюсь вызвать init с обратным вызовом, определенным в 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")

Но я не могу понять, как я должен это делать сделать такой массив обратных вызовов:

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

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

У кого-нибудь есть идея?

Ответы [ 2 ]

0 голосов
/ 29 марта 2020

Листинг [Python 3.Docs]: ctypes - библиотека сторонних функций для Python.

Самое простое - взять C код, как есть (не задумываясь об этом), и преобразовать его в 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]

И остальной код ( Я не вставил его в консоль, так как он не будет работать):

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

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

lib.init(callback_array)

Всегда определять argtypes restype ) для функций, импортируемых из .dll s. Проверьте [SO]: функция C, вызванная из Python через ctypes, возвращает неправильное значение (ответ @ CristiFati) для получения более подробной информации.

0 голосов
/ 28 марта 2020

Работающий минимальный пример был бы хорош. Я создал один ниже, но если он не работает для вас, обновите ваш вопрос с подобным примером DLL, который соответствует вашей ситуации:

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)

Выход:

callback1 1 2
callback2 True False
callback3 1 2 3
...