2

Trying to create a python callback which needs to be invoked while calling the C callback from a dll in Windows environment. Please review the code below to understand the issue.

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

The DLL Documents the Prototype and the callback for the SetByteTotalsCallback() method as shown below.

Prototype :

ULONG QCWWANAPI2K SetSessionStateCallback( tFNSessionState pCallback );

Callback :

void ByteTotalsCallback( ULONGLONG  txTotalBytes, ULONGLONG rxTotalBytes );

OUTPUT :

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

The current problem is that the whole program gets called properly, but the python callback does not get called at all. The program exits with 0 status from gobiDLL.SetByteTotalsCallback(callback, c_byte(256)) method, but the callback() method written in python does not called during the call.

Could you please point out what could help enter the python callback ? The other sample qsort() method passes the pointer to the python function pointer wonderfully. At a loss to get the root cause of the issue here.

TIA,

Anthony

2
  • If you could reduce the issue to a minimal reproducible scenario, it would be much easier to help you. Thanks! Commented Jun 20, 2011 at 14:54
  • I suggest making the example even shorter (I suspect you can half the code again), but additionally, given that this isn't a common situation, perhaps consider providing a visual studio solution that someone can just compile and see for themselves. Commented Jun 20, 2011 at 14:56

1 Answer 1

6

You can't. C/C++ functions can't access Python functions directly - that function prototype is probably expecting a pointer to C. Python will be passing it a pointer to its internal data structure for that particular function.

This is the time to build a C extension to python to wrap that DLL and expose it to Python. What you'd do is essentially have the C callback call the Python callback, since that can be done. To be clearer, what you want to achieve is:

                | 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  <-------
                |

The following is a very quick explanation for building a calling a python function from C. You'll need to build this code with the MSVC (or alternative tool) that was used to build your Python distribution; use depends.exe to find out which msvcXX.dll it is linked against.

Global state is generally considered bad, but for simplicity that's what I used:

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

I then added a set handler function to make the process of setting the callback easier. However, you don't need to do that, you just need to

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

The next line is the important one - I allow passing of two python objects (the O arguments to PyArg_ParseTuple. One object contains the function, the other its parameters.

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

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

Sort out references. Python needs you to do this.

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

You can then call this elsewhere with:

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

Don't forget the DECREF, you need Python to gc the arglist.

From python, using this is as simple as:

set_event_handler(func, some_tuple)

Where func has matching parameters like so:

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

Things you probably want to read up on:

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer 9Fingers ! We would evaluate creating a C Extension for the Callback access. Hope this resolves our question. I was hoping on a change to the ctypes mechanism of function pointer call similar to the way python callback in qsort (from cdll.msvcrt dll) is being called.
A Few Questions which could help clarify the understanding - 1. Does the Python Callback/Wrapper written and mapped using ctypes not get registered. I presume it works because there is a extension underneath that supports callabcks. 2. The example of qsort has a function with 2 pointers which are called correctly, why would the method from external dll not register the callback. Is the failure due to the non existent c extension for processing the callbacks ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.