2016-11-12 72 views
2

我有函数指针的零终止阵列下面的C代码:如何访问Rust中C声明的零终止数组指针?

#include <stdio.h> 

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

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

此代码被编译并使用货物构建脚本链接到我的锈程序。我如何从Rust中调用数组中的每个函数指针?

+0

还有我读到另一个问题完全向后想后我对工作反正答案... – Shepmaster

回答

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时,数组左值被强制为一个指针。这并不意味着变量存储一个指针!

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

我们使用Option<extern "C" fn()>为null的函数指针如FFI chapter of The Rust Programming Language描述。

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

这一切都感觉很janky,所以如果有一个更好的解决方案可我也不会感到惊讶,但这并工作。

+0

什么janky一个关于它的小的可能性?它看起来完全像我期望的! :-) – BurntSushi5

+0

@ BurntSushi5主要围绕不得不参考。我花了很多时间思考我做了我的“正常”函数指针错误,它有太多的引用层。我花了一段时间才意识到我没有足够的*。 – Shepmaster

+1

我不太明白为什么你会得到相同的表格和函数地址。你在println!()中被auto-deref欺骗了吗?我用'gcc -fPIC -shared funcref.c -o funcref.so'编译了你的C代码,并且用'objdump -t'查看这个.so文件,这两个代码在不同的地址和不同的部分。 –

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()>为null的函数指针作为described here

这种输出,对我来说:

0x7efc27d37030 
0x7efc27b366f0 
Calling startup 
hello_register called 

我之所以说“几乎”是,我不知道怎么说这是一个未知大小的数组。:-)

+0

我必须为未知大小提供的唯一解决方法是给它一个'1'的大小(因为这是最小大小),然后在循环时忽略它......但我不确定编译器是否可以对此做了奇怪的优化。 –

+0

或@ Shepmaster的回答为零。 –