How do I access a zero-terminated array of functio

2019-07-21 10:26发布

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?

3条回答
Rolldiameter
2楼-- · 2019-07-21 11:06

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. :-)

查看更多
看我几分像从前
3楼-- · 2019-07-21 11:07

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.

查看更多
够拽才男人
4楼-- · 2019-07-21 11:11

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.

查看更多
登录 后发表回答