int sum(const int* my_array, int length) { int total = 0;
for(int i = 0; i < length; i++) { total += my_array[i]; } return total;}
use std::os::raw::c_int;
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()); }}Rust這邊,將數組中的 int 元素傳到C函數中執行相加運算。int本身這種基礎類型,默認按值傳遞(copy一份傳遞)。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); }}將Rust中初始化的結構體,轉換成指針,傳遞到C函數中進行調用。本身只是借用讀一下(不寫)。這個結構體的所有權一直在 Rust 這邊,由 t 掌控(表述不完全準確,但基本上是這個意思。原因是抽象降級到C這一層的時候,就不再自動分辨所有權了)。生命期結束時,由Rust的RAII規則,自動銷毀。以後,我們對於int這種自帶 Copy(或按值傳遞)的類型,就不重點關注了,兩邊對照寫就行了,沒有什麼有難度的地方在裡面。Rust 調用 C,內存在 C 這邊分配,在Rust中進行填充為了分析清楚這個場景,我們設計了一個例子。在實現的過程中,遇到了相當多的坑。這方面的資料,中英文都非常缺乏。好在,經過一番摸索,最後算是找到了正確的方法。在C端,設計一個結構體,欄位有整型,字符串,浮點型在C端,malloc一塊內存,是一個n個結構體實例組成的數組C端,導出三個函數。create, print, release在Rust中,調用C的create函數,創建一個資源,並拿到指針在Rust中,利用這個指針,填充C中管理的結構體數組假如我們創建了一個名為 rustffi 的cargo工程。
#include<stdio.h>#include<stdlib.h>#include<malloc.h>
typedef struct Students { int num; int total; char name[20]; float scores[3];} Student;
Student* create_students(int n) { if (n <= 0) return NULL; Student *stu = NULL; stu = (Student*) malloc(sizeof(Student)*n);
return stu;}
void release_students(Student *stu) { if (stu != NULL) free(stu);}
void print_students(Student *stu, int n) { int i; for (i=0; i<n; i++) { printf("C side print: %d %s %d %.2f %.2f %.2f\n", stu[i].num, stu[i].name, stu[i].total, stu[i].scores[0], stu[i].scores[1], stu[i].scores[2]); }}gcc -fPIC -shared -o libcfoo.so cfoo.cuse std::os::raw::{c_int, c_float};use std::ffi::CString;use std::slice;
#[repr(C)]#[derive(Debug)]pub struct Student { pub num: c_int, pub total: c_int, pub name: [u8; 20], pub scores: [c_float; 3],}
#[link(name = "cfoo")]extern "C" { fn create_students(n: c_int) -> *mut Student; fn print_students(p_stu: *mut Student, n: c_int); fn release_students(p_stu: *mut Student);}
fn main() { let n = 3; unsafe { let p_stu = create_students(n as c_int); assert!(!p_stu.is_null());
let s: &mut [Student] = slice::from_raw_parts_mut(p_stu, n as usize); for elem in s.iter_mut() { elem.num = 1 as c_int; elem.total = 100 as c_int;
let c_string = CString::new("Mike").expect("CString::new failed"); let bytes = c_string.as_bytes_with_nul(); elem.name[..bytes.len()].copy_from_slice(bytes);
elem.scores = [30.0 as c_float, 40.0 as c_float, 30.0 as c_float]; }
println!("rust side print: {:?}", s);
print_students(p_stu, n as c_int);
release_students(p_stu); } println!("Over.");}RUSTFLAGS='-L .' cargo build編譯。這裡,RUSTFLAGS='-L .' 指定要連結的 so 的目錄。我把上面生成的 libcfoo.so 放到了工程根目錄,因此,指定路徑為 .,其它類推。LD_LIBRARY_PATH="." target/debug/rustffirust side print: [Student { num: 1, total: 100, name: [77, 105, 107, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], scores: [30.0, 40.0, 30.0] }, Student { num: 1, total: 100, name: [77, 105, 107, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], scores: [30.0, 40.0, 30.0] }, Student { num: 1, total: 100, name: [77, 105, 107, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], scores: [30.0, 40.0, 30.0] }]C side print: 1 Mike 100 30.00 40.00 30.00C side print: 1 Mike 100 30.00 40.00 30.00C side print: 1 Mike 100 30.00 40.00 30.00Over.可以看到,達到了我們的預期目標:在Rust中,修改C中創建的結構體數組內容。完整可運行代碼在:https://github.com/daogangtang/learn-rust/tree/master/08rustffi要點(踩坑)分析C和Rust的結構體定義,兩邊要保持一致。typedef struct Students { int num; int total; char name[20]; float scores[3];} Student;pub struct Student { pub num: c_int, pub total: c_int, pub name: [u8; 20], pub scores: [c_float; 3],}pub struct Student { pub num: c_int, pub total: c_int, pub name: *mut c_char, pub scores: [c_float; 3],}結果可以編譯通過,但是一運行就發生段錯誤。讀者可以想一想為什麼?:D關於C中數組指針的翻譯問題fn create_students(n: c_int) -> *mut Student;*mut Student 感覺只是指向一個實例的指針,或者說分不清是一個實例還是一個實例數組。對,發現這點就對了,C語言裡面,這個就是這樣的,也不分(。。。從現在來看這個設計,其實有點奇葩)。所以C裡面,在知道指針的情況下,還需要一個長度數據才能準確界定一個數組。既然這樣,那我們就這樣寫就行了。另外兩個接口中的參數也是類似情況,不再說明。神器 slice
Rust的slice提供的兩個方法:slice::from_raw_parts() 和 slice::from_raw_parts_mut()。這個東西是神器。實現了我們這個場景下的核心要求,資源在C那邊管理,Rust這邊只是借用。但是填數據又是在Rust這邊。搜索標準庫,我們會發現,Vec也有這兩個方法。這其實是對應的。slice的這兩個方法,不獲取數據的所有權。Vec的這兩個方法,獲取數據的所有權(必要的時候,會進行完全Copy一份)。於是可以看到,Rust中的所有權基礎,直接影響到了API的設計和使用。C字符串的細節let c_string = CString::new("Mike").expect("CString::new failed"); let bytes = c_string.as_bytes_with_nul();這裡這個 as_bytes_with_nul() 就是轉成字節的時候,帶上後面的 '\0'。
elem.name[..bytes.len()].copy_from_slice(bytes);這個目的就是把我們生成的數據源slice,填充到目標slice,也就是成員的 name 字符中去。elem.name[0] = b'M';elem.name[1] = b'i';elem.name[2] = b'k';elem.name[3] = b'e';elem.name[4] = b'\0';c_charc_char 內部定義為 i8,我們這裡用的 u8,關係不大,用 c_char 的話,用 as 操作符轉一下就好了。所有權分析整個Rust代碼,實際就是調用了C導出的函數。C那邊的數據資源,完全由C自己掌控,分配和釋放都是C函數自己做的(這點非常重要)。Rust這邊只是可變借用,然後填充了數據。因為在這種跨FFI邊界調用的情況下,內存的分配,完全可能不是同一個分配器做的,混用會出各種 undefined behaviour。所以,這些細節一定要注意。同時也可以看到,Rust和C竟然可以這樣玩兒?Rust太強大了。除了C++,我暫時還想不到其它有什麼語言能直接與C這樣互操作的。
Rust 調 C,數據在 Rust 這邊生成,在C中進行處理