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