2

I have the following C code with a zero-terminated array of function pointers:

#include <stdio.h>

void hello_register(void) {
  printf("hello_register called\n");
}

void (*vlog_startup_routines[])() = {
    hello_register,
    0
};

This code is compiled and linked to my Rust program using a Cargo build script. How can I call each of the function pointers in the array from Rust?

1
  • There's a small possibility I read another question completely backwards and wanted to post the answer I worked on anyway... Commented Nov 12, 2016 at 20:07

3 Answers 3

4

A combination of the previous two answers looks nicer:

extern crate libc;

type VlogStartupRoutine = Option<extern "C" fn()>;

extern "C" {
    // This array is NULL-terminated; set the length to zero to
    // prevent any uncontrolled access.
    static vlog_startup_routines: [VlogStartupRoutine; 0];
}

fn main() {
    unsafe {
        let routines = vlog_startup_routines.as_ptr();

        for i in 0.. {
            match *routines.offset(i) {
                Some(routine) => {
                    println!("Calling startup routine #{}", i);
                    routine();
                }
                None => break,
            }
        }
    }
}

The symbol vlog_startup_routines is not a pointer to a function pointer, it's an array of function pointers. When you use the name vlog_startup_routines in C code, the array lvalue is coerced to a pointer. That doesn't mean that the variable stores a pointer!

To most closely express this in Rust, we can define vlog_startup_routines as an array. The problem is that we don't know how many elements are in the array because it's NULL-terminated. To prevent any accidental misuse, we set the length to zero and only access elements though offsets of the raw pointer.

We use Option<extern "C" fn()> for the nullable function pointer as described in the FFI chapter of The Rust Programming Language.

Sign up to request clarification or add additional context in comments.

Comments

3
+100

The problem here is that vlog_startup_routines is not a pointer. If you declare it as a pointer; it is an array. The symbol resolves to the address of the first item of the array. In C, if you have:

int i = 7;
int a[1] = { 8 };
int *p = &i;

then at the linker level, the symbol i is the address of the location containing the value 7, a is also the address of a location containing an integer value (8), and p is the address of a location containing a pointer to an integer. Another way of saying it is that the linker symbol is always the address of the variable.

If you declare it as:

// Or whatever argument types
type VlogStartupRoutine = extern "C" fn();

extern "C" {
    static vlog_startup_routines: VlogStartupRoutine;
}

you're saying that vlog_startup_routines is a variable containing a function pointer, more like the C void *vlog_startup_routines.

unsafe {
    println!("{:p}", vlog_startup_routines);
    println!("{:p}", hello_register as *const ());
}

it's dereferencing taking the value stored at the address vlog_startup_routines, which is indeed the first pointer.

The correct (nearly) code is:

type VlogStartupRoutine = Option<extern "C" fn()>;

#[link(name = "funcref")]
extern "C" {
    static vlog_startup_routines: [VlogStartupRoutine;10];
    fn hello_register();
}

fn main() {
    unsafe {
        println!("{:p}", vlog_startup_routines.as_ptr());
        println!("{:p}", hello_register as *const ());
    }
    unsafe {
        let routine = vlog_startup_routines[0].unwrap();
        println!("Calling startup");
        routine();
        assert!(vlog_startup_routines[1].is_none());
    }
}

Note that I use Option<extern "C" fn()> for the nullable function pointer as described here.

This outputs, for me:

0x7efc27d37030
0x7efc27b366f0
Calling startup
hello_register called

The reason I say "nearly" is that I'm not sure how to say it's an unknown-sized array. :-)

2 Comments

The only work-around I have to offer for the unknown size is to give it a size of 1 (because that's the minimum size), and then ignore it when looping... but I am not sure whether the compiler can do weird optimizations on this.
Or zero as in @Shepmaster's answer.
1

You can call a single function pointer easily enough:

extern crate libc;

// Or whatever argument types
type VlogStartupRoutine = extern "C" fn();

extern "C" {
    static vlog_startup_routines: VlogStartupRoutine;
}

fn main() {
    unsafe {
        let routine = vlog_startup_routines;
        println!("Calling startup");
        routine();
    }
}

However, note that we and the C compiler are doing some trickery here: the array and the first element of the array have the same value:

extern "C" {
    static vlog_startup_routines: VlogStartupRoutine;
    fn hello_register();
}

fn main() {
    unsafe {
        println!("{:p}", vlog_startup_routines);
        println!("{:p}", hello_register as *const ());
    }
}
0x1029bf750
0x1029bf750

To work around this, we grab a reference to the initial function and then use that to iterate though each of the function pointers. I've renamed vlog_startup_routines just to prevent any accidental misuse of it.

extern crate libc;

// Or whatever appropriate argument types
type VlogStartupRoutine = extern "C" fn();

extern "C" {
    #[link_name = "vlog_startup_routines"]
    static INITIAL_VLOG_STARTUP_ROUTINE: VlogStartupRoutine;
}

fn main() {
    unsafe {
        let startup_routines: *const VlogStartupRoutine = &INITIAL_VLOG_STARTUP_ROUTINE;

        for i in 0.. {
            let routine = *startup_routines.offset(i);

            let routine_as_ptr = routine as *const ();
            if routine_as_ptr.is_null() { break }

            println!("Calling startup routine #{}", i);
            routine();
        }
    }
}

This all feels pretty janky, so I wouldn't be surprised if there were a better solution available, but this does work.

8 Comments

What's janky about it? It looks exactly like I would expect! :-)
@BurntSushi5 mostly around having to take the reference. I spent so much time thinking I did my "normal" function pointer mistake, which is having too many layers of references. It took me a while to realize I didn't have enough.
I don't quite understand why you're getting the same address for the table and function. Are you being fooled by an auto-deref in println!()? I compiled your C code with gcc -fPIC -shared funcref.c -o funcref.so, and looking at the .so with objdump -t has the two at different addresses, and in different sections.
@BurntSushi5: The type of INITIAL_VLOG_STARTUP_ROUTINE seems janky; vlog_startup_routines is a pointer to a function pointer, not a function pointer itself. Faking it and then taking it address to start iterating seems like a round-about way to go about things.
@MatthieuM.: vlog_startup_routines is not a pointer to a function pointer, it's an array of function pointers. When you use the name vlog_startup_routines in C code, the array lvalue is coerced to a pointer. That doesn't mean that the variable stores a pointer! The expression &INITIAL_VLOG_STARTUP_ROUTINE in Rust code yields the same value as the expression vlog_startup_routines in C code.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.