Using ctypes to write callbacks that pass-in point

2019-07-23 06:23发布

I am using ctypes to interface python code with a legacy C DLL. The DLL expects that I give it a function pointer to be used as a callback, that then will be called from within the DLL.

The C declaration of that callback looks like

char foo(char **buf)

Semantically the DLL expects that I let point buf to a string buffer that is managed by the callback and return 0 if everything works fine. In C that would be a static char[] variable mybuf and I would pass the address of that static buf back with code like

*buf = &mybuf[0] ;
return (char)0 ;

The string in mybuf needs to be null terminated as usual in C. I was able to pass a function pointer to the DLL and now a python (member) function

def pyfoo(self, char_ptr_ptr) 

gets called.

The class in which pyfoo is defined also initialises (in init) a field

self.response=create_string_buffer("The Response String")

How do I pass the address, where this string is actually stored in memory to the DLL? Since ctypes sometimes creates copies instead of returning a real reference by address I tried something like

cpointer = c_int.from_address(char_ptr_ptr[0])
cpointer.value = addressof(self.response)
return c_char(0)

It does not work. I tried other ways of dereferencing the passed pointer-pointer and storing a memory address there (e.g. directly storing (what I think is the address of the string) into char-ptr_ptr[0]), but the caller of the callback is never doing what I am expecting it to do.

If anybody has ever solved the problem to create a null terminated string in python and to pass the address of that string through a **pointer to a DLL implemented in C, I would be very thankful to here about how he/she solved the issue. Thanks!

(Edit) Or is my problem much simpler? Is

c_char(0) 

actually pushing a single byte onto the stack as return value? If not, what would be the right way to implement a callback that shall return a single byte?

1条回答
手持菜刀,她持情操
2楼-- · 2019-07-23 07:06

I'd use the following function prototype:

CFUNCTYPE(c_byte, POINTER(POINTER(c_char)))

And write pyfoo like this:

    def pyfoo(self, char_ptr_ptr):
        char_ptr_ptr[0] = self.response
        return 0

For example:

>>> import os
>>> from ctypes import *

>>> open('tmp.c', 'w').write(r'''
... #include <stdio.h>
... typedef char (*callback_t)(char **buf);
... int test(callback_t foo)
... {
...     char res, *buf;
...     res = foo(&buf);
...     printf("res: %d\n", res);
...     printf("buf: %s\n", buf);
...     return 0;
... }
... ''')
>>> _ = os.system('gcc -shared -fPIC -o tmp.so tmp.c')
>>> lib = CDLL('./tmp.so')

>>> class Test(object):
...   def __init__(self):
...     self.response = create_string_buffer("The Response String")
...     self.callback = CFUNCTYPE(c_byte, POINTER(POINTER(c_char)))(self.pyfoo)
...   def pyfoo(self, char_ptr_ptr):
...     char_ptr_ptr[0] = self.response
...     return 0
... 

>>> t = Test()
>>> _ = lib.test(t.callback)
res: 0
buf: The Response String
查看更多
登录 后发表回答