如何从 Rust 访问用 C 声明的函数指针的零终止数组?
How do I access a zero-terminated array of function pointers declared in C from Rust?
我有以下带有零终止函数指针数组的 C 代码:
#include <stdio.h>
void hello_register(void) {
printf("hello_register called\n");
}
void (*vlog_startup_routines[])() = {
hello_register,
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_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();
}
}
}
这一切都感觉很糟糕,所以如果有更好的解决方案,我不会感到惊讶,但这确实有效。
这里的问题是 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
我说 "nearly" 的原因是我不确定如何说它是一个未知大小的数组。 :-)
前两个答案的组合看起来更好:
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 中所述。
我有以下带有零终止函数指针数组的 C 代码:
#include <stdio.h>
void hello_register(void) {
printf("hello_register called\n");
}
void (*vlog_startup_routines[])() = {
hello_register,
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_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();
}
}
}
这一切都感觉很糟糕,所以如果有更好的解决方案,我不会感到惊讶,但这确实有效。
这里的问题是 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
我说 "nearly" 的原因是我不确定如何说它是一个未知大小的数组。 :-)
前两个答案的组合看起来更好:
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 中所述。