如何从Rust中访问C中声明的零终止函数指针数组?

时间:2016-11-12 20:06:39

标签: rust function-pointers ffi

我有以下C代码,其中包含零终止的函数指针数组:

#include <stdio.h>

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

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

使用Cargo构建脚本编译并链接到我的Rust程序。如何从Rust中调用数组中的每个函数指针?

3 个答案:

答案 0 :(得分:4)

前两个答案的组合看起来更好:

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,
            }
        }
    }
}

符号vlog_startup_routines不是指向函数指针的指针,它是一个函数指针数组。在C代码中使用名称vlog_startup_routines时,数组左值被强制转换为指针。这并不意味着变量存储指针!

为了在Rust中最密切地表达这一点,我们可以将vlog_startup_routines定义为数组。问题是我们不知道数组中有多少元素,因为它以NULL结尾。为了防止任何意外误操作,我们将长度设置为零,并且只能通过原始指针的偏移来访问元素。

我们使用Option<extern "C" fn()>作为可空函数指针,如FFI chapter of The Rust Programming Language中所述。

答案 1 :(得分:3)

这里的问题是vlog_startup_routines不是指针。如果将其声明为指针;这是一个数组。符号解析为数组第一项的地址。在C中,如果你有:

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

然后在链接器级别,符号i是包含值7的位置的地址,a 包含整数值的位置的地址(8),p是包含指向整数的指针的位置的地址。另一种说法是链接符号始终是变量的地址。

如果您将其声明为:

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

extern "C" {
    static vlog_startup_routines: VlogStartupRoutine;
}

你说vlog_startup_routines是一个包含函数指针的变量,更像是C void *vlog_startup_routines

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

取消引用存储在地址vlog_startup_routines的值,这确实是第一个指针。

正确的(近乎)代码是:

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());
    }
}

请注意,我将Option<extern "C" fn()>用于可空函数指针described here

这为我输出:

0x7efc27d37030
0x7efc27b366f0
Calling startup
hello_register called

我说“差不多”的原因是我不确定如何说它是一个未知大小的数组。 : - )

答案 2 :(得分:1)

您可以轻松地调用单个函数指针:

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();
    }
}

但是,请注意我们和C编译器在这里做了一些技巧:数组和数组的第一个元素具有相同的值:

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

要解决这个问题,我们获取对初始函数的引用,然后使用它来迭代每个函数指针。我已重命名为vlog_startup_routines,以防止意外误用。

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();
        }
    }
}

这一切都让人感到非常笨拙,所以如果有更好的解决方案,我不会感到惊讶,但这确实有效。