將Rust編譯成庫
上一章講述瞭如何從rust中調用c庫,這一章我們講如何把rust編譯成庫讓別的語言通過cffi調用。
調用約定和mangle
正如上一章講述的,為了能讓rust的函數通過ffi被調用,需要加上extern "C"
對函數進行修飾。
但由於rust支持重載,所以函數名會被編譯器進行混淆,就像c++一樣。因此當你的函數被編譯完畢後,函數名會帶上一串表明函數簽名的字符串。
比如:fn test() {}
會變成_ZN4test20hf06ae59e934e5641haaE
.
這樣的函數名為ffi調用帶來了困難,因此,rust提供了#[no_mangle]
屬性為函數修飾。
對於帶有#[no_mangle]
屬性的函數,rust編譯器不會為它進行函數名混淆。如:
#[no_mangle]
extern "C" fn test() {}
在nm中觀察到為
...
00000000001a7820 T test
...
至此,test
函數將能夠被正常的由cffi
調用。
指定crate
類型
rustc
默認編譯產生rust
自用的rlib
格式庫,要讓rustc
產生動態鏈接庫或者靜態鏈接庫,需要顯式指定。
- 方法1: 在文件中指定。
在文件頭加上
#![crate_type = "foo"]
, 其中foo
的可選類型有bin
,lib
,rlib
,dylib
,staticlib
.分別對應可執行文件, 默認(將由rustc
自己決定),rlib
格式,動態鏈接庫,靜態鏈接庫。 - 方法2: 編譯時給rustc 傳
--crate-type
參數。參數內容同上。 - 方法3: 使用cargo,指定
crate-type = ["foo"]
,foo
可選類型同1
小技巧: Any
由於在跨越ffi
過程中,rust
類型信息會丟失,比如當用rust
提供一個OpaqueStruct
給別的語言時:
use std::mem::transmute;
#[derive(Debug)]
struct Foo<T> {
t: T
}
#[no_mangle]
extern "C" fn new_foo_vec() -> *const c_void {
Box::into_raw(Box::new(Foo {t: vec![1,2,3]})) as *const c_void
}
#[no_mangle]
extern "C" fn new_foo_int() -> *const c_void {
Box::into_raw(Box::new(Foo {t: 1})) as *const c_void
}
fn push_foo_element(t: &mut Foo<Vec<i32>>) {
t.t.push(1);
}
#[no_mangle]
extern "C" fn push_foo_element_c(foo: *mut c_void){
let foo2 = unsafe {
&mut *(foo as *mut Foo<Vec<i32>>) // 這麼確定是Foo<Vec<i32>>? 萬一foo是Foo<i32>怎麼辦?
};
push_foo_element(foo3);
}
以上代碼中完全不知道foo
是一個什麼東西。安全也無從說起了,只能靠文檔。
因此在ffi
調用時往往會喪失掉rust
類型系統帶來的方便和安全。在這裡提供一個小技巧:使用Box<Box<Any>>
來包裝你的類型。
rust
的Any
類型為rust
帶來了運行時反射的能力,使用Any
跨越ffi
邊界將極大提高程序安全性。
use std::any::Any;
#[derive(Debug)]
struct Foo<T> {
t: T
}
#[no_mangle]
extern "C" fn new_foo_vec() -> *const c_void {
Box::into_raw(Box::new(Box::new(Foo {t: vec![1,2,3]}) as Box<Any>)) as *const c_void
}
#[no_mangle]
extern "C" fn new_foo_int() -> *const c_void {
Box::into_raw(Box::new(Box::new(Foo {t: 1}) as Box<Any>)) as *const c_void
}
fn push_foo_element(t: &mut Foo<Vec<i32>>) {
t.t.push(1);
}
#[no_mangle]
extern "C" fn push_foo_element_c(foo: *mut c_void){
let foo2 = unsafe {
&mut *(foo as *mut Box<Any>)
};
let foo3: Option<&mut Foo<Vec<i32>>> = foo2.downcast_mut(); // 如果foo2不是*const Box<Foo<Vec<i32>>>, 則foo3將會是None
if let Some(value) = foo3 {
push_foo_element(value);
}
}
這樣一來,就非常不容易出錯了。