Search code examples
pythoncythonctypes

ctypes: get the actual address of a c function


This is tricky (at least to me :-) , maybe unfeasible. But I try to ask to you.

I have this c shared library:

#include <stdio.h>
#include <stdlib.h>

static int variable = -666;

int get_value() {
    return variable;
}

void print_pointer_to_get_value() {
    printf("pointer_to_get_value: %p\n", &get_value);
}

Compiled this way (on Linux):

gcc -fPIC -c -O2 shared.c && gcc -shared -o shared.so shared.o

Now I load the library and call print_pointer_to_get_value():

>>> import ctypes
>>> so = ctypes.cdll.LoadLibrary('./shared.so')
>>> so.print_pointer_to_get_value()
pointer_to_get_value: 0x7f46e178f700

I'd like to get from ctypes the actual address, as integer, of the get_value function as printed by print_pointer_to_get_value(). My final target is to move that address to a Cython module and call that function inside a "nogil" Cython function. I need to load the .so library at runtime, therefore I cannot compile my Cython module linking it to the library.

Thanks 1000.


Solution

  • It's a nasty multistep process that isn't easy to do elegantly:

    Some Cython code:

    ctypedef double (*math_function_t)(double) nogil
    
    import ctypes
    
    def call_f(f, double x):
        cdef math_function_t cy_f_ptr = (<math_function_t*><size_t>ctypes.addressof(f))[0]
    
        cdef double res
        with nogil:
            res = cy_f_ptr(x)
        return res
    

    Here I pass Cython a Ctypes function type (f) and get the address in Cython. I don't think it's possible to obtain the address in Python. As an example of how you might initialise f, on Linux you could do:

    lib = ctypes.cdll.LoadLibrary("libm.so.6")
    f = lib.sin
    
    call_f(f,0.5) # returns sin(0.5)
    

    (to use the standard library sin function).

    The Cython line cdef math_function_t cy_f_ptr = (<math_function_t*><size_t>ctypes.addressof(f))[0] can be broken down as follows:

    1. ctypes.addressof(f) gets the address that the ctypes variable f is held in. __This is NOT the value you're after_ - it's the place where the value you're after is stored.
    2. This is cast first to a size_t integer then to a pointer to a cdef function pointer type. Cython requires the two step cast.
    3. [0] dereferences your math_function_t* to get a math_function_t. This is the function pointer (i.e. the value you want)

    The information for this answer was got from this newsgroup thread (which I can't currently access)