Search code examples
pythoncstructfunction-pointersctypes

C to Python via Ctypes - Wrapping Struct of Function Pointers to Static Functions


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.


Solution

  • [Python.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.

    dll00.h:

    struct Data {
        int i;
    };
    
    
    typedef struct {
        int (* const Func00Ptr) (struct Data*, const char*);
    } DataFunc;
    
    
    extern DataFunc const dataFunc;
    

    dll00.c:

    #include <stdio.h>
    
    #include "dll00.h"
    
    
    static int func00(struct Data *pData, const char *source)
    {
        printf("From C - Data.i: [%d], source: [%s]\n", pData->i, source);
        return -255;
    }
    
    
    DataFunc const dataFunc = { &func00 };
    

    code00.py:

    #!/usr/bin/env python
    
    import ctypes as ct
    import sys
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    class Data(ct.Structure):
        _fields_ = (
            ("i", ct.c_int),
        )
    
    
    Func00Type = ct.CFUNCTYPE(ct.c_int, ct.POINTER(Data), ct.c_char_p)
    
    
    class DataFunc(ct.Structure):
        _fields_ = (
            ("func00", Func00Type),
        )
    
    
    def main(*argv):
        data = Data(127)
        dll = ct.CDLL(DLL_NAME)
        data_func = DataFunc.in_dll(dll, "dataFunc")
        ret = data_func.func00(ct.byref(data), "abcd".encode())
        print("Function returned: {:d}".format(ret))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.")
        sys.exit(rc)
    

    Output:

    [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049962265]> ~/sopr.sh
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]> ls
    dll00.c  dll00.h  code00.py
    [064bit prompt]> gcc -shared -fPIC -o dll00.so dll00.c
    [064bit prompt]> ls
    dll00.c  dll00.h  code.py  dll00.so
    [064bit prompt]> objdump -t dll00.so | grep dataFunc
    0000000000200e10 g     O .data.rel.ro   0000000000000008              dataFunc
    [064bit prompt]> python code00.py
    Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] 064bit on linux
    
    From C - Data.i: [127], source: [abcd]
    Function returned: -255
    
    Done.