cffi doesn't allow creating pointers to existing C functions

Issue #265 closed
Antoine Pitrou
created an issue

Browsing through the docs, it isn't possible to create a function pointer to a plain existing C function. cffi has ffi.callback which allows to create a C function pointer to a Python function; but you can't just create a C function pointer from either the function's name (symbol) or its numeric address. ctypes lets you do it from the function's address.

(for the record, in my use case, the function doesn't exist at the C level, as it's generated by LLVM, but the basic point stands)

Comments (8)

  1. Armin Rigo

    You'll need to describe this point in more details. What exactly do you want to do? As far as I can tell, this gives you a pointer to an existing function:

        ffi.cdef("size_t strlen(char *);")
        lib = ffi.dlopen("c")
        print lib.strlen
    

    Or, in API mode:

        from _my_cffi_module import ffi, lib
        print ffi.addressof(lib, "function_name")
    
  2. Antoine Pitrou reporter

    Ah, thanks, ffi.cast seems to do the trick. It seems I can even do ffi.cast("void *", numeric_address) and use the resulting cdata as a function pointer. Is that supported?

  3. Antoine Pitrou reporter

    By the way, it's a pity that cffi compares type by name, rather than structurally:

    TypeError: initializer for ctype 'int(*)(int)' must be a pointer to same type, not cdata 'int32_t(*)(int32_t)'
    
  4. Armin Rigo

    What do you mean, "use as a function pointer"? You can't call a void *, no. But you can assign pass it as argument to a C function that expects a function pointer. This is all the same as in C (and possibly different than ctypes, which is not a criteria I use often, I admit).

    About "comparing types by names", the rules are the same as C as far as I know, with the following exception: all stdint.h types (like int32_t) behave as if they where their own primitive types in CFFI, like int or long.

    Let me explain in more detail. It seems nonsense because intand int32_t should be often the same thing. However, the problem is that we can't easily reproduce exactly what C does.

    For example, on 64-bit, the types longand long long are of the same size, but actually incompatible with each other. So GCC gives a warning when you mix the types long(*)(long) and long long(*)(long long) even though they look the same if you run on a 64-bit platform. So this part we reproduce; we do reproduce it not only because C does it, but actually because it makes sense: mixing long and long long, even on 64-bit, is a bad idea because that code would not compile correctly on 32-bit, where the two types are suddenly different. (The same issue occurs for the seemingly equal intversus long on 32-bit, which stop being equal on 64-bit.)

    The part above makes some sense, but then stdint.h is added on top of that as a regular C header. This stdint.h defines int64_t as being either long or long long, one of the two---but which one? I believe the answer is not standardized. And yes, it makes a difference: try long *p; int64_t *q = p; or long long *p; int64_t *q = p;, exactly one of the two will generate a warning. We can't easily learn which one it is, apart from trying to run GCC on these two examples and checking which one generates a warning, not an error---pretty much an "ugh please no" solution.

    Similarly, int32_t is clearly defined as int on 64-bit, but on 32-bit it could be either int or long. I have seen some header file for some library which actually uses long on 32-bit instead of just using always using int. So assuming some platform does the same for stdint.h, then on that platform, the types int(*)(int) and int32_t(*)(int32_t) are incompatible (though equal) in the eyes of GCC. So on that hypothetical platform, your exact example fails.

    That's why CFFI makes them incompatible everywhere.

    I believe it is better to complain as soon as possible, rather than to implement the opposite ctypes-like solution for CFFI, which is that all integers of the same size are identical (which can easily lead to crashes on a different platform).

    "In the face of ambiguity, refuse the temptation to guess": I generally follow the idea that if C code does or could compile with warnings, then that's not good enough for CFFI. My motivation for that is that an ignored warning can lead to a segfault; for the kind of error we're talking about here, in C it is often relatively easy to track in a debugger, and C programmers are generally expected to do that; but in Python with CFFI (and possibly libffi) layers on top, using a C-level debugger is another story entirely.

  5. Antoine Pitrou reporter

    Hum, it seems my own understanding of C was lacking. The problem here is when you want cffi to interoperate with other type systems such as ctypes' or Numba's (or Numpy's), where there aren't distinct types for e.g. int and int32_t (if int is 32 bits). Then translating a ctypes or Numba signature into a cffi signature is ambiguous: if you have a 32-bit int, should you spell it int or int32_t (or perhaps long on a 32-bit machine)?

    Currently the user-friendly solution for us is to return a void * pointer, which is obviously less reliable.

  6. Armin Rigo

    Indeed, you can't safely take programmatically a ctypes declaration and translate it back to C. I think you'd need to look at the Python source code to know how the types are written there. For example, if ctypes says that the return type of a function is ctypes.c_long and you happen to be running on 32-bit, then you don't know if this c_long was originally written as ctypes.c_int or ctypes.c_long in the Python source code, so you don't know if you should generate an int or a long. Maybe you need some hack like first patch ctypes so that the objects ctypes.c_int and ctypes.c_long are distinguishable even on 32-bit...

  7. Log in to comment