本篇是《手动绑定 C 库入门》的第二篇。了解第一篇后,我们知道在调用 C 库时,需要重新在 Rust 中对该 C 库中的数据类型和函数签名进行封装。这篇我们将实践涉及到诸如数组,结构体等类型时,如何进行手动绑定。
备注:有自动生成绑定的工具,比如, bindgen可以自动生成 C 库和某些C ++库的 Rust FFI 绑定。但这个章节不涉及这些。
repr属性c_utils.so,其中有一个函数 int sum(const int* my_array, int length) ,给定一个整数数组,返回数组中所有元素的和。// ffi/rust-call-c/src/c_utils.cint sum(const int* my_array, int length) {int total = 0;for(int i = 0; i < length; i++) {total += my_array[i];}return total;}
// ffi/rust-call-c/src/array.rsuse 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
类型布局(Type layout),是指类型在内存中的排列方式,是其数据在内存中的大小,对齐方式以及其字段的相对偏移量。当数据自然对齐时,CPU 可以最有效地执行内存读写。
repr属性repr属性来指定类型的内存布局,该属性支持的值有:#[repr(Rust)],默认布局或不指定repr属性。#[repr(C)],C 布局,这告诉编译器"像C那样对类型布局",可使用在结构体,枚举和联合类型。#[repr(transparent)],此布局仅可应用于结构体为:PhantomData<T>)#[repr(u*)],#[repr(i*)],原始整型的表示形式,如:u8,i32,isize等,仅可应用于枚举。align和packed来提高或降低其对齐方式。使用repr属性,只可以更改其字段之间的填充,但不能更改字段本身的内存布局。repr(packed)可能导致未定义的行为,不要轻易使用。repr属性的一些示例:ffi/rust-call-c/src/layout.rsuse 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() {::size_of::<CStruct>(), 6);::align_of::<CStruct>(), 2);::align_of::<PackedStruct>(), 1);::align_of::<AlignedStruct>(), 8);::size_of::<ExampleUnion>(), 2);}
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;}
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()) {=> r,=> panic!("Invalid UTF-8 sequence: {}", e),};: {}", s);}}
struct object;struct object* init(void);void free_object(struct object*);int get_info(const struct object*);void set_info(struct object*, int);
#[repr(C)]pub struct OpaqueObject {_private: [u8; 0],}
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.rsfn 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
extern type语法,但目前还未稳定。repr属性#[repr(C)]确保有一致的内存布局。原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。