Rust FFI 编程 - 手动绑定 C 库入门 02

本篇是《手动绑定 C 库入门》的第二篇。了解第一篇后,我们知道在调用 C 库时,需要重新在 Rust 中对该 C 库中的数据类型和函数签名进行封装。这篇我们将实践涉及到诸如数组,结构体等类型时,如何进行手动绑定。

备注:有自动生成绑定的工具,比如,bindgen可以自动生成 C 库和某些C ++库的 Rust FFI 绑定。但这个章节不涉及这些。

本篇的主要内容有:

  • 数组示例

  • 结构体示例

    • repr属性

    • 结构体

    • opaque 结构体

1. 数组示例

假定我们现在有个 C 库 c_utils.so,其中有一个函数 int sum(const int* my_array, int length) ,给定一个整数数组,返回数组中所有元素的和。

// ffi/rust-call-c/src/c_utils.c
int sum(const int* my_array, int length) {    int total = 0;
    for(int i = 0; i < length; i++) {        total += my_array[i];    }        return total;}

在 Rust 中绑定 C 库中的 sum 函数,然后直接通过 unsafe 块中调用。

// ffi/rust-call-c/src/array.rs
use std::os::raw::c_int;
// 对 C 库中的 sum 函数进行 Rust 绑定:extern "C" {    fn sum(my_array: *const c_int, length: c_int) -> c_int;}
fn main() {    let numbers: [c_int; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    unsafe {        let total = sum(numbers.as_ptr(), numbers.len() as c_int);        println!("The total is {}", total);
        assert_eq!(total, numbers.iter().sum());    }}

编译,然后执行输出如下结果:

lyys-MacBook-Pro:src lyy$ rustc array.rs -o array -L. -lc_utilslyys-MacBook-Pro:src lyy$ ./arrayThe total is 55

2. 结构体

结构体是由用户定义的一种复合类型,我们知道不同的语言使用不同的机制在计算机内存中布局数据,这样 Rust 编译器可能会执行某些优化而导致类型布局有所不同,无法和其他语言编写的程序正确交互。

类型布局(Type layout),是指类型在内存中的排列方式,是其数据在内存中的大小,对齐方式以及其字段的相对偏移量。当数据自然对齐时,CPU 可以最有效地执行内存读写。

2.1 repr属性

为了解决上述问题,Rust 引入了repr属性来指定类型的内存布局,该属性支持的值有:

  1. #[repr(Rust)],默认布局或不指定repr属性。

  2. #[repr(C)],C 布局,这告诉编译器"像C那样对类型布局",可使用在结构体,枚举和联合类型。

  3. #[repr(transparent)],此布局仅可应用于结构体为:

  • 包含单个非零大小的字段( newtype-like ),以及

  • 任意数量的大小为 0 且对齐方式为 1 的字段(例如PhantomData<T>

  • #[repr(u*)],#[repr(i*)],原始整型的表示形式,如:u8i32isize等,仅可应用于枚举。

  • 结构体的成员总是按照指定的顺序存放在内存中,由于各种类型的对齐要求,通常需要填充以确保成员以适当对齐的字节开始。对于 1 和 2 ,可以分别使用对齐修饰符alignpacked来提高或降低其对齐方式。使用repr属性,只可以更改其字段之间的填充,但不能更改字段本身的内存布局。repr(packed)可能导致未定义的行为,不要轻易使用。

    以下是repr属性的一些示例:

    // ffi/rust-call-c/src/layout.rs
    use std::mem;
    // 默认布局,对齐方式降低到 1#[repr(packed(1))]struct PackedStruct {    first: i8,    second: i16,    third: i8}
    // C 布局#[repr(C)]struct CStruct {    first: i8,    second: i16,    third: i8}
    // C 布局, 对齐方式升高到 8#[repr(C, align(8))]struct AlignedStruct {    first: i8,    second: i16,    third: i8}
    // 联合类型的大小等于其字段类型的最大值#[repr(C)]union ExampleUnion {    smaller: i8,    larger: i16}
    fn main() {    assert_eq!(mem::size_of::<CStruct>(), 6);    assert_eq!(mem::align_of::<CStruct>(), 2);        assert_eq!(mem::align_of::<PackedStruct>(), 1);    assert_eq!(mem::align_of::<AlignedStruct>(), 8);
        assert_eq!(mem::size_of::<ExampleUnion>(), 2);}
    
    
    2.2 结构体

    为了说明在 Rust 中调用 C 库时,应该如何传递结构体?我试着找了一些 C 库,但由于有些库需要安装,最后决定通过标准库中的 time.h 来做示例。我们假定要在 Rust 程序中实现格式化日期格式的功能,可以通过调用这个标准库中的 strftime() 函数来完成。首先看头文件 time.h ,结构体及函数声明如下:

    struct tm {
       int tm_sec;         /* 秒,范围从 0 到 59                */
       int tm_min;         /* 分,范围从 0 到 59                */
       int tm_hour;        /* 小时,范围从 0 到 23                */
       int tm_mday;        /* 一月中的第几天,范围从 1 到 31        */
       int tm_mon;         /* 月份,范围从 0 到 11                */
       int tm_year;        /* 自 1900 起的年数                */
       int tm_wday;        /* 一周中的第几天,范围从 0 到 6         */
       int tm_yday;        /* 一年中的第几天,范围从 0 到 365        */
       int tm_isdst;       /* 夏令时                        */
    };
    
    
    size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
    

    该函数根据 format 中定义的格式化规则,格式化结构体 timeptr 表示的时间,并把它存储在 str 中。这个函数使用了指向 C 结构体 tm 的指针,该结构体也必须在 Rust 中重新声明,通过类型布局小节,我们知道可以使用repr属性#[repr(C)]来确保在 Rust 中,该结构体的内存布局与在 C 中相同。

    以下是对 strftime() 函数的 Rust FFI 手动绑定示例:

    use libc::{c_int, size_t};
    #[repr(C)]pub struct tm {    pub tm_sec: c_int,    pub tm_min: c_int,    pub tm_hour: c_int,    pub tm_mday: c_int,    pub tm_mon: c_int,    pub tm_year: c_int,    pub tm_wday: c_int,    pub tm_yday: c_int,    pub tm_isdst: c_int,}
    
    extern {    // 标准库<time.h> strftime函数的 Rust FFI 绑定    #[link_name = "strftime"]    pub fn strftime_in_rust(stra: *mut u8, maxsize: size_t, format: *const u8, timeptr: *mut tm) -> size_t;}
    
    

    接下来我们编写 Rust 程序,调用这个 C 库函数实现日期格式化功能,代码如下:

    use std::str;
    mod time;
    fn main() {    // 初始化    let mut v: Vec<u8> = vec![0; 80];    // 初始化结构体    let mut t = time::tm {        tm_sec: 15,        tm_min: 09,        tm_hour: 18,        tm_mday: 14,        tm_mon: 04,        tm_year: 120,        tm_wday: 4,        tm_yday: 135,        tm_isdst: 0,    };    // 期望的日期格式    let format = b"%Y-%m-%d %H:%M:%S\0".as_ptr();        unsafe {        // 调用        time::strftime_in_rust(v.as_mut_ptr(), 80, format, &mut t);
            let s = match str::from_utf8(v.as_slice()) {            Ok(r) => r,            Err(e) => panic!("Invalid UTF-8 sequence: {}", e),        };            println!("result: {}", s);    }}
    
    
    2.3 Opaque 结构体

    一些 C 库的 API 通常是在不透明指针指向的结构体上运行的一系列的函数。比如有以下 C 代码:

    struct object;
    struct object* init(void);void free_object(struct object*);int get_info(const struct object*);void set_info(struct object*, int);
    
    

    目前在 Rust 中,比较推荐的一种做法是,通过使用一个拥有私有字段的结构体来声明这种类型。

    #[repr(C)]pub struct OpaqueObject {    _private: [u8; 0],}
    
    

    同样的,对该 C 库中的函数进行 Rust FFI 手动绑定,示例如下:

    extern "C" {    pub fn free_object(obj: *mut OpaqueObject);    pub fn init() -> *mut OpaqueObject;    pub fn get_info(obj: *const OpaqueObject) -> c_int;    pub fn set_info(obj: *mut OpaqueObject, info: c_int);}
    
    

    接下来我们调用这些函数,代码如下:

    // ffi/rust-call-c/src/opaque.rs
    fn main() {    unsafe {        let obj = init();        println!("Original value: {}", get_info(obj));
            set_info(obj, 521);        println!("New value: {}", get_info(obj));    }}
    
    

    编译,然后执行输出如下结果:

    lyys-MacBook-Pro:src lyy$ rustc opaque.rs -o opaque -L. -lffi_testlyys-MacBook-Pro:src lyy$ ./opaqueOriginal value: 0New value: 521
    
    

    注意:有一个 RFC 1861  ( 链接:https://github.com/canndrew/rfcs/blob/extern-types/text/1861-extern-types.md)用于引入extern type语法,但目前还未稳定。

    总结

    在 Rust 中调用 C 库,进行 Rust FFI 绑定:

    • 传递结构体类型的参数时,可以使用repr属性#[repr(C)]确保有一致的内存布局。

    • 对于 C 库中的 Opaque 结构体类型的参数,在 Rust 中可以使用一个拥有私有字段的结构体来表示。

    本文代码主要参考:https://github.com/lesterli/rust-practice/tree/master/ffi

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值