上一章講述了如何從rust中調(diào)用c庫,這一章我們講如何把rust編譯成庫讓別的語言通過cffi調(diào)用。
正如上一章講述的,為了能讓rust的函數(shù)通過ffi被調(diào)用,需要加上extern "C"
對(duì)函數(shù)進(jìn)行修飾。
但由于rust支持重載,所以函數(shù)名會(huì)被編譯器進(jìn)行混淆,就像c++一樣。因此當(dāng)你的函數(shù)被編譯完畢后,函數(shù)名會(huì)帶上一串表明函數(shù)簽名的字符串。
比如:fn test() {}
會(huì)變成_ZN4test20hf06ae59e934e5641haaE
.
這樣的函數(shù)名為ffi調(diào)用帶來了困難,因此,rust提供了#[no_mangle]
屬性為函數(shù)修飾。
對(duì)于帶有#[no_mangle]
屬性的函數(shù),rust編譯器不會(huì)為它進(jìn)行函數(shù)名混淆。如:
#[no_mangle]
extern "C" fn test() {}
在nm中觀察到為
...
00000000001a7820 T test
...
至此,test
函數(shù)將能夠被正常的由cffi
調(diào)用。
crate
類型rustc
默認(rèn)編譯產(chǎn)生rust
自用的rlib
格式庫,要讓rustc
產(chǎn)生動(dòng)態(tài)鏈接庫或者靜態(tài)鏈接庫,需要顯式指定。
#![crate_type = "foo"]
, 其中foo
的可選類型有bin
, lib
, rlib
, dylib
, staticlib
.分別對(duì)應(yīng)可執(zhí)行文件,
默認(rèn)(將由rustc
自己決定), rlib
格式,動(dòng)態(tài)鏈接庫,靜態(tài)鏈接庫。--crate-type
參數(shù)。參數(shù)內(nèi)容同上。crate-type = ["foo"]
, foo
可選類型同1Any
由于在跨越ffi
過程中,rust
類型信息會(huì)丟失,比如當(dāng)用rust
提供一個(gè)OpaqueStruct
給別的語言時(shí):
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
是一個(gè)什么東西。安全也無從說起了,只能靠文檔。
因此在ffi
調(diào)用時(shí)往往會(huì)喪失掉rust
類型系統(tǒng)帶來的方便和安全。在這里提供一個(gè)小技巧:使用Box<Box<Any>>
來包裝你的類型。
rust
的Any
類型為rust
帶來了運(yùn)行時(shí)反射的能力,使用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將會(huì)是None
if let Some(value) = foo3 {
push_foo_element(value);
}
}
這樣一來,就非常不容易出錯(cuò)了。