从Rust暴露一个包含函数指针零终止数组的C符号给C使用

4

我有以下的C代码,它被编译为一个.so文件:

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

在Rust中,我可以使用#[no_mangle]声明函数。我该如何公开一个名为vlog_startup_routines的包含函数指针且以零结尾的数组符号?

有趣。数组不过是一个指针,而且以0结尾是运行时属性。但我不确定如何在FFI边界上传递指针到函数;你是否查看了现有的Rust文档? - Matthieu M.
@MatthieuM.,这里基本上不存在数组指针;(void *)hello_register == (void *)vlog_startup_routines,对吗? - Shepmaster
我建议在提问时注意措辞。提供 C 代码会让人觉得你想要调用那段代码。最好提供使用 extern 的 C 代码,以突出你想在 Rust 中实现该符号。此外,当你有一个库时,没有东西可以驱动程序,因此不会调用任何内容。使用 C 可执行文件会更好地表达问题。 - Shepmaster
@Shepmaster:注意一下,*hello_register == vlog_startup_routines,因为数组包含函数的指针而不是函数本身。如果想要使用多个函数,这一点很重要。所以在 Rust 里,hello_register 就会像 *const fn() 这样,允许进行算术运算。 - Matthieu M.
@MatthieuM. 或许你可以帮我弄清楚为什么我的代码说它们是相同的 - Shepmaster
1个回答

2
您需要定义一个类型为数组的static项目。在定义static项目时,不幸的是,我们需要指定该数组的大小(截至Rust 1.13.0)。
在Rust中,函数指针不被认为是不安全的调用(除非您有一个unsafe fn)。然而,一个空指针是不安全的调用,所以Rust不允许创建一个空函数指针。但有一个技巧:当T是一个指针(任何类型的指针,包括fat pointers和function pointers),Option<T>T的大小相同1,而None只是表示为空指针。因此,我们可以定义一个Option<fn()>值的数组来获得所需的结果。 1对于其他类型,Option<T>将比T大,以存储判别式。
#[no_mangle]
#[allow(non_upper_case_globals)]
pub static vlog_startup_routines: [Option<fn()>; 2] = [
    Some(hello_register),
    None
];

如果必须指定数组大小让您感到烦恼,那么您可以使用一个计算数组大小的宏来代替。作为额外的奖励,这个宏会在每个函数中添加尾随的None并将其包装在Some中。
macro_rules! one_for {
    ($_x:tt) => (1)
}

macro_rules! vlog_startup_routines {
    ($($func:expr,)*) => {
        #[no_mangle]
        #[allow(non_upper_case_globals)]
        pub static vlog_startup_routines: [Option<fn()>; $(one_for!($func) +)* 1] = [
            $(Some($func),)*
            None
        ];
    }
}

vlog_startup_routines! {
    hello_register,
}

注意:存在one_for宏是因为我们需要在重复模式中引用一个参数符号(您可以有多个不同的重复,因此编译器需要知道您正在引用哪个),但我们不关心其值。

当然是None啦!我无法弄清如何创建一个具有适当类型的NULL。唉。 - Shepmaster
谢谢,我考虑过这个选项,但我想可能有更好的替代方案,因为只有最后一个元素应该是0。我猜现在还没有。 - jck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接