Function pointers can be created by using ctypes.CFUNCTYPE. The first argument of its constructor is the return type of the function pointer, and the remaining arguments are the parameter types of the function it points to. E.g. int (*)(double, double) corresponds to CFUNCTYPE(c_int, c_double, c_double).
For example, here's a demonstration that uses C's qsort to sort an array. This is adapted from the ctypes documentation's example on callback functions, which doesn't set qsort.argtypes.
from ctypes import * import random import platform libc_locations = {"Windows": "msvcrt.dll", "Darwin": "libc.dylib"} libc = cdll.LoadLibrary(libc_locations.get(platform.system(), "libc.so.6")) # int (*)(void *, void *) CMP_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p) # void qsort(void *base, size_t nmemb, size_t size, int (*compar)(void *, void *)); libc.qsort.argtypes = [c_void_p, c_size_t, c_size_t, CMP_FUNC] libc.qsort.restype = None @CMP_FUNC def int_cmp(a: int, b: int) -> int: # `c_void_p` automatically gets converted to `int` by `ctypes`, # so they need to be passed to `c_void_p`'s constructor again. # Then cast the void pointers to int pointers and dereference them. a_val = cast(c_void_p(a), POINTER(c_int)).contents.value b_val = cast(c_void_p(b), POINTER(c_int)).contents.value return -1 if a_val < b_val else 1 if a_val > b_val else 0 # allocate C array of 10 integers and fill with the numbers 1 ~ 10 a = (c_int * 10)(*range(1, 11)) random.shuffle(a) print("Before sort:", tuple(a)) libc.qsort(a, len(a), sizeof(a._type_), int_cmp) print("After sort:", tuple(a))
To convert a Python function to a function pointer, either define it with a CFUNCTYPE object as a decorator, or pass the function to the CFUNCTYPE object later on (when you need to pass the Python function to a C function).
CFUNCTYPE objects with identical constructor arguments would be treated as the same type by ctypes, but you should still save the resulting object to a variable so you don't have to repeat the same code again when you need to convert a Python function to an instance of that function pointer type.
So now that we know how to create function pointers, we can start writing the declarations for your types. Starting with the structures, your declaration for struct sys already looks correct. So I first looked for the declaration of the gsl_rng structure and found that it is this:
typedef struct { const gsl_rng_type *type; void *state; } gsl_rng;
and the declaration of the gsl_rng_type structure (which gsl_rng references) is this:
typedef struct { const char *name; unsigned long int max; unsigned long int min; size_t size; void (*set)(void *state, unsigned long int seed); unsigned long int (*get)(void *state); double (*get_double)(void *state); } gsl_rng_type;
These declarations only contain fundamental data types and various pointer types, so they can easily be translated to Python (keeping your struct sys the same):
from ctypes import * SET_FUNC = CFUNCTYPE(None, c_void_p, c_ulong) GET_FUNC = CFUNCTYPE(c_ulong, c_void_p) GET_DOUBLE = CFUNCTYPE(c_double, c_void_p) class gsl_rng_type(Structure): _fields_ = [ ("name", c_char_p), ("max", c_ulong), ("min", c_ulong), ("size", c_size_t), ("set", SET_FUNC), ("get", GET_FUNC), ("get_double", GET_DOUBLE) ] class gsl_rng(Structure): _fields_ = [ ("type", POINTER(gsl_rng_type)), ("state", c_void_p) ] class sys(Structure): _fields_ = [ ("alpha", c_double), ("sigma", c_double) ]
Now that we have the necessary structure definitions, we can write the prototype for your function:
SYS_CALLBACK = CFUNCTYPE( None, POINTER(c_double), POINTER(c_double), c_double, POINTER(sys) ) foo.argtypes = [ POINTER(c_double), POINTER(c_double), SYS_CALLBACK, SYS_CALLBACK, POINTER(sys), POINTER(c_double), c_double, c_double, c_int, c_int, POINTER(gsl_rng) ] foo.restype = c_int # assuming your function returns int
To call your function, you would call the SET_FUNC, GET_FUNC, GET_DOUBLE, and SYS_CALLBACK variables (which are all instances of CFUNCTYPE) to convert Python functions to function pointers, ctypes.pointer to create pointers to objects (which you would need to initialize the gsl_rng structure, whose type field expects a pointer to an instance of gsl_rng_type), and ctypes.byref to pass ctypes objects by reference to your function (using ctypes.pointer also works in this case, but is slower).
If your C library is very large, you might save time by using SWIG to generate the Python interface instead of writing it manually.