如何从Rust访问C中声明的以零结尾的函数指针数组?

如何从Rust访问C中声明的以零结尾的函数指针数组?,rust,function-pointers,ffi,Rust,Function Pointers,Ffi,我有以下带有以零结尾的函数指针数组的C代码: #include <stdio.h> void hello_register(void) { printf("hello_register called\n"); } void (*vlog_startup_routines[])() = { hello_register, 0 }; #包括 作废hello_登记簿(作废){ printf(“hello_寄存器称为\n”); } void(*vlog_启动_例程[

我有以下带有以零结尾的函数指针数组的C代码:

#include <stdio.h>

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

void (*vlog_startup_routines[])() = {
    hello_register,
    0
};
#包括
作废hello_登记簿(作废){
printf(“hello_寄存器称为\n”);
}
void(*vlog_启动_例程[])()={
你好!,
0
};
这段代码是使用Cargo构建脚本编译并链接到我的Rust程序的。如何从Rust调用数组中的每个函数指针?

您可以很容易地调用单个函数指针:

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\u启动\u例程
,只是为了防止意外误用它

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

这一切都让人感觉很不舒服,所以如果有更好的解决方案,我不会感到惊讶,但这确实有效。

这里的问题是
vlog\u启动\u例程
不是指针。如果您将其声明为指针;这是一个数组。符号解析为数组第一项的地址。在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\u启动\u例程
是一个包含函数指针的变量,更像是C
void*vlog\u启动\u例程

unsafe {
    println!("{:p}", vlog_startup_routines);
    println!("{:p}", hello_register as *const ());
}
它使用存储在地址
vlog\u startup\u例程
中的值解除引用,这实际上是第一个指针

正确(接近)的代码是:


我之所以说“几乎”是因为我不知道如何说它是一个大小未知的数组。:-)

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

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,
            }
        }
    }
}
extern板条箱libc;
类型VlogStartupRoutine=选项;
外部“C”{
//此数组以NULL结尾;请将长度设置为零以
//防止任何不受控制的进入。
静态vlog_启动_例程:[vlog启动例程;0];
}
fn main(){
不安全{
let routines=vlog_startup_routines.as_ptr();
因为我在0{
匹配*例程。偏移量(i){
一些(常规)=>{
println!(“调用启动例程#{}”,i);
例程();
}
无=>中断,
}
}
}
}

符号
vlog\u startup\u routines
不是指向函数指针的指针,而是函数指针数组。当您在C代码中使用名称
vlog\u startup\u routines
时,数组左值被强制为指针。这并不意味着变量存储指针

为了用Rust最接近地表达这一点,我们可以将
vlog\u启动\u例程
定义为一个数组。问题是我们不知道数组中有多少个元素,因为它以NULL结尾。为了防止任何意外的误用,我们将长度设置为零,并且只通过原始指针的偏移量访问元素


我们使用选项作为可为空的函数指针,如中所述。

有一个小的可能性,我完全反向阅读了另一个问题,并想发布我研究的答案…这有什么不好的地方?看起来和我期望的一模一样!:-)@BurntSushi5主要是围绕着必须采取的参考。我花了太多时间思考我犯了“正常”函数指针错误,这就是引用层太多。我花了一段时间才意识到我的钱不够。我不太明白为什么表和函数的地址都是一样的。你是不是被println里的一个自动删除程序愚弄了!()? 我用
gcc-fPIC-shared funcref.C-o funcref.so
编译了您的C代码,并查看了.so with
objdump-t
在不同的地址和不同的部分有两个。@BurntSushi5:INITIAL\u VLOG\u STARTUP\u例程的类型看起来很简陋<代码>vlog_启动_例程是指向函数指针的指针,而不是函数指针本身。假装它然后取它的地址开始迭代似乎是一种迂回的方式。@MatthieuM.:
vlog_startup_例程
不是指向函数指针的指针,而是函数指针数组。当您在C代码中使用名称
vlog\u startup\u routines
时,数组左值被强制为指针。这并不意味着变量存储指针!Rust code中的表达式
&INITIAL\u VLOG\u STARTUP\u ROUTINE
产生的值与C代码中的表达式
VLOG\u STARTUP\u routines
的值相同。对于未知大小,我唯一需要解决的问题是给它一个
1
(因为这是最小大小),然后在循环时忽略它。。。但我不确定编译器是否可以在这个问题上进行奇怪的优化,或者在@Shepmaster的回答中为零。
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,
            }
        }
    }
}