C to Python via Ctypes - Wrapping Struct of Functi

2020-02-13 03:29发布

问题:

I have structs in a C library that are like this. The function pointers in DataFn point to static functions.

.h

struct Data {
    int i;
    int *array;
};

typedef struct {
    bool (* const fn1) (struct Data*, const char *source);
    ....
} DataFn;
extern DataFn const DATAFUNC

Using objdump, the table only contains DATAFUNC and a few other things from gcc.

This is fine in C where calling fn1 would go like DATAFUNC.fn1(..., ...), but how would something like this be wrapped around so fn1 can be called in python w/ ctypes?

Example python

libc = ctypes.cdll.LoadLibrary("./data.so")
print(libc.DATAFUNC)

results in <_FuncPtr object at 0x6ffffcd7430>

This is similar, but there isn't a factory function.

回答1:

[Python 3.Docs]: ctypes - A foreign function library for Python contains everything required to solve this problem.

I believe that the main piece missing, was the in_dll method of a ctypes type (Accessing values exported from dll section).

Other than that, in order to work with C data, you need to let Python know of the data format. That applies to:

  • structs. Define Python counterparts by subclassing ctypes.Structure
  • Function pointers (applies to your case). Define them using ctypes.CFUNCTYPE

I prepared a simplified example that illustrates the above. Note that I didn't do any error handling (checking for NULLs (which you should)), to keep things simple.

c.h:

struct Data {
    int i;
};


typedef struct {
    int (* const fn1) (struct Data*, const char*);
} DataFn;


extern DataFn const DATAFUNC;

c.c:

#include <stdio.h>
#include "c.h"


static int func1(struct Data *pData, const char *source) {
    printf("From C - Data.i: [%d], source: [%s]\n", pData->i, source);
    return -255;
}


DataFn const DATAFUNC = {&func1};

code00.py:

#!/usr/bin/env python3


import sys
from ctypes import c_int, c_char_p, Structure, CDLL, CFUNCTYPE, POINTER, byref


class Data(Structure):
    _fields_ = [
        ("i", c_int),
    ]


fn1_type = CFUNCTYPE(c_int, POINTER(Data), c_char_p)


class DataFn(Structure):
    _fields_ = [
        ("fn1", fn1_type),
    ]


def main():
    data = Data(127)
    dll = CDLL("./c.so")
    data_func = DataFn.in_dll(dll, "DATAFUNC")
    ret = data_func.fn1(byref(data), "abcd".encode())
    print("DATAFUNC.fn1 returned {:d}".format(ret))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Output:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> ls
c.c  c.h  code00.py
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> gcc -shared -fPIC -o c.so c.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> ls
c.c  c.h  code.py  c.so
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> objdump -t c.so | grep DATAFUNC
0000000000200e10 g     O .data.rel.ro   0000000000000008              DATAFUNC
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> python3 code00.py
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux

From C - Data.i: [127], source: [abcd]
DATAFUNC.fn1 returned -255