Обертка Python для C Callback - PullRequest
2 голосов
/ 20 июня 2011

Попытка создать обратный вызов Python, который должен быть вызван при вызове обратного вызова C из библиотеки DLL в среде Windows.Пожалуйста, просмотрите код ниже, чтобы понять проблему.

from ctypes import *

#---------qsort Callback-------------#
IntArray5 = c_int * 5
ia = IntArray5(5,1,7,33,99)
libc = cdll.msvcrt
qsort = libc.qsort
qsort.restype = None

CMPFUNC = CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int) )
test = 0
def py_cmp_func(a,b):
    #print 'py_cmp_func:',a[0],b[0]
    global test
    test = 10000
    return a[0]-b[0]

cmp_func = CMPFUNC(py_cmp_func)
qsort(ia, len(ia), sizeof(c_int), cmp_func)
print "global test=",test
for item in ia : print item

#----------Load DLL & Connect ------------#
gobiDLL = WinDLL("C:\LMS\QCWWAN2k.dll")
print  'Output of connect : ',gobiDLL.QCWWANConnect()

#----------SetByteTotalsCallback----------#
tx = POINTER(c_ulonglong)
rx = POINTER(c_ulonglong)
proto_callback = WINFUNCTYPE(c_void_p,tx,rx)

gtx = grx = 0 # Used to copy the response in the py_callback
def py_callback(t,r):
    sleep(10)
    print 'python callback ...'
    print "tx=",t,"rx=",r
    global gtx,grx
    gtx = 5000 # gtx = t
    grx = 2000 # grx = r
    #return 0

callback = proto_callback(py_callback)
gobiDLL.SetByteTotalsCallback.restype = c_ulong
gobiDLL.SetByteTotalsCallback.argtypes = [proto_callback,c_byte]                    

print "SetByteTotalsCallback = ",gobiDLL.SetByteTotalsCallback(callback, c_byte(256))
print "gtx = ",gtx
print "grx = ",grx

DLL документирует прототип и обратный вызов для метода SetByteTotalsCallback (), как показано ниже.

Прототип:

ULONG QCWWANAPI2K SetSessionStateCallback( tFNSessionState pCallback );

Обратный вызов:

void ByteTotalsCallback( ULONGLONG  txTotalBytes, ULONGLONG rxTotalBytes );

ВЫХОД:

>>> 
global test= 10000
1
5
7
33
99
Output of connect :  0
SetByteTotalsCallback =  0
gtx =  0
grx =  0
>>>>

В настоящее время проблема заключается в том, что вся программа вызывается правильно, но обратный вызов python вообще не вызывается.Программа завершает работу с состоянием 0 из метода gobiDLL.SetByteTotalsCallback (callback, c_byte (256)), но метод callback (), написанный на python, не вызывается во время вызова.

Не могли бы вы указать, что может помочьввести обратный вызов Python?Другой пример метода qsort () прекрасно передает указатель на указатель на функцию python.В затруднении, чтобы получить основную причину проблемы здесь.

TIA,

Энтони

1 Ответ

6 голосов
/ 20 июня 2011

Вы не можете. Функции C / C ++ не могут напрямую обращаться к функциям Python - этот прототип функции, вероятно, ожидает указатель на C. Python будет передавать ему указатель на свою внутреннюю структуру данных для этой конкретной функции.

Настало время создать расширение C для Python, чтобы обернуть эту DLL и открыть ее для Python. По сути, вы должны сделать так, чтобы обратный вызов C вызывал обратный вызов Python, так как это можно сделать . Чтобы быть более понятным, то, что вы хотите достичь, это:

                | This side is C land i.e. "real" addresses
                |
Python objects --> C extension -- register callback with --> DLL
                |                                             |
 in the python  |                                     Calls callback
                |                                             |
  interpreter <-------------- Callback in C extension  <-------
                |

Ниже приведено очень быстрое объяснение построения вызова функции Python из C. Вам нужно будет создать этот код с помощью MSVC (или альтернативного инструмента), который использовался для сборки вашего дистрибутива Python; используйте depends.exe, чтобы узнать, с каким msvcXX.dll он связан.

Глобальное состояние обычно считается плохим, но для простоты я использовал его:

static PyObject* pyfunc_event_handler = NULL;
static PyObject* pyfunc_event_args = NULL;

Затем я добавил функцию set set handler, чтобы упростить процесс установки обратного вызова. Однако вам не нужно этого делать, вам просто нужно

static PyObject* set_event_handler(PyObject *self, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

Следующая строка является важной - я разрешаю передавать два объекта python (O аргументы PyArg_ParseTuple. Один объект содержит функцию, другой - ее параметры.

    if (PyArg_ParseTuple(args, "OO", &temp, &pyfunc_event_args)) {

        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be a function");
            return NULL;
        }

Сортировка ссылок. Python нуждается в вас, чтобы сделать это.

        Py_XINCREF(temp);                    /* Add a reference to new func */
        Py_XDECREF(pyfunc_event_handler);    /* Dispose of previous callback */
        pyfunc_event_handler = temp;         /* Remember new callback */

        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

Затем вы можете позвонить в другое место с помощью:

 PyObject* arglist = Py_BuildValue("(O)", pyfunc_event_args);
 pyobjresult = PyObject_CallObject(pyfunc_event_handler, arglist);
 Py_DECREF(arglist);

Не забудьте DECREF, вам нужен Python для gc arglist.

Из python использовать это так же просто, как:

set_event_handler(func, some_tuple)

Где func имеет подходящие параметры:

def func(obj):
    /* handle obj */

Вещи, которые вы, вероятно, хотите прочитать:

...