前言
受到2022年“谷歌使用Rust重寫Android系統(tǒng)且所有Rust代碼的內(nèi)存安全漏洞為零” [1] 的啟發(fā),最近筆者懷著濃厚的興趣也順應(yīng)Rust 的潮流,嘗試著將一款C語言開發(fā)的基礎(chǔ)軟件轉(zhuǎn)化為 Rust 語言。本文的主要目的是通過記錄此次轉(zhuǎn)化過程中遇到的比較常見且有意思的問題以及解決此問題的方法與大家一起做相關(guān)的技術(shù)交流和討論。
問題描述
本文將記錄轉(zhuǎn)化過程中遇到的另外一個問題。該問題是由已經(jīng)轉(zhuǎn)化完成的 Rust 代碼使用到軟件中引入的第三方軟件包和鏈接庫所導(dǎo)致的。設(shè)想這樣一個場景:Rust 項目中完成某一個功能點需要用到一個或多個第三方軟件包和鏈接庫。這顯然是很常見的用戶場景,但是由于用戶環(huán)境不同,用戶安裝的第三方軟件包和鏈接庫的版本不同,使得轉(zhuǎn)化后的 Rust 代碼必須要做適當(dāng)?shù)募嫒萏幚怼?/p>
這里所說的用戶的環(huán)境不同,可以理解為芯片指令集的平臺不同,如 Intel x86 以及國產(chǎn)的 ARM 麒麟服務(wù)器。當(dāng)然更常見的情形是芯片平臺相同,但是存在操作系統(tǒng)層面第三方軟件包和鏈接庫安裝的差異,如 x86 下的 Ubuntu 和 CentOS 中用戶安裝了不同版本的第三方軟件包和鏈接庫等。
事實上,即使排除所有平臺和系統(tǒng)層面的差異,由于用戶安裝了該基礎(chǔ)軟件所依賴的不同版本的第三方軟件包和鏈接庫,然而這些第三方軟件包或者鏈接庫由于自身的演進(jìn)導(dǎo)致不同版本之間存在較大差異(可能實現(xiàn)相同功能的函數(shù)和函數(shù)簽名都有千差萬別),這給我重寫該軟件的工作帶來了一些挑戰(zhàn)。基于上述說明,在完成重寫該基礎(chǔ)軟件的過程中如何使得轉(zhuǎn)化后的 Rust 代碼能兼容該基礎(chǔ)軟件所依賴的主流第三方軟件包和鏈接庫則是我遇到的最大挑戰(zhàn)。需要說明的是這里的第三方軟件包和鏈接庫可能是基于 Rust 語言開發(fā)的,也可能是基于 C 語言開發(fā)的。
解決方案
對于此問題的解決方案需要使用 Rust FFI(Foreign Function Interface) [1],這基本上是沒有太大爭議的。因為在本次軟件重寫過程中我遇到的場景是:對于不同版本的鏈接庫使用哪個版本的函數(shù)取決于用戶的安裝運行時環(huán)境,所以除了 Rust FFI,在代碼適配上我還考慮了使用 Rust features [2] 機制。
下面我簡化了一下場景和解決方案,同時我把樣本代碼放到了我的 github [3] 里,歡迎大家一起交流。如樣本代碼所示,my-rust-bin 文件夾中的一段業(yè)務(wù)代碼需要調(diào)用到靜態(tài)鏈接庫 my_rust_lib 中的函數(shù),該鏈接庫有兩個版本 v1(在文件夾 my-rust-lib-v1 中) 和 v2(在文件夾 my-rust-lib-v2 中), 且不同版本的庫其函數(shù)不一樣。
my-rust-lib-v1 對應(yīng)的業(yè)務(wù)函數(shù)為:pub fn my_rust_lib_v1(left: usize, right: usize) -> usize
my-rust-lib-v2 對應(yīng)的業(yè)務(wù)函數(shù)為:pub fn my_rust_lib_v2(left: usize, right: usize) -> usize
另外一個 lib 文件夾的目的其實是為了模擬用戶本地安裝的鏈接庫。可以分別編譯不同版本的靜態(tài)鏈接庫,然后把生成的庫文件(在本例中是)libmy_rust_lib.a, 然后把不同版本的庫文件拷貝到此文件夾下,以此來模擬用戶環(huán)境中安裝的不同版本的鏈接庫。解決方案中的關(guān)鍵點在于 my-rust-bin 中,
首先在 my-rust-bin 的 Cargo.toml 中有定義對應(yīng)的 features,如下所示:
[features] v1=[] v2=[]
其次在 my-rust-bin 的 src/main.rs 下的代碼如下:
#[cfg(feature="v1")] modbindingmylib{ extern"C"{ pubfnmy_rust_lib_v1(left:usize,right:usize)->usize; } } #[cfg(feature="v2")] modbindingmylib{ extern"C"{ pubfnmy_rust_lib_v2(left:usize,right:usize)->usize; } } #[cfg(not(any(feature="v1",feature="v2")))] compile_error!("Pleasespecifyeither'v1'or'v2'feature"); pubfnmy_rust_lib(left:usize,right:usize)->usize{ #[cfg(feature="v1")] unsafe{ returnbindingmylib::my_rust_lib_v1(left,right); } #[cfg(feature="v2")] unsafe{ returnbindingmylib::my_rust_lib_v2(left,right); } } fnmain(){ letr_value:usize=my_rust_lib(3,5); println!("Thereturnvalueofmy_rust_libis[{}]",r_value); }
現(xiàn)在我來解讀一下這段代碼。代碼先分別定義一個相同的模塊 bindingmylib,然后根據(jù) features 分別引入的依賴,使用的不同的靜態(tài)鏈接庫函數(shù)(my_rust_lib_v1 和 my_rust_lib_v2), 同時通過 compile_error! 定義一個沒有設(shè)置 v1 和 v2 features 的編譯錯誤(防止編譯時忘記設(shè)置 features選項,下面在編譯環(huán)節(jié)的時候有用)。最后將兩個有差異的函數(shù)統(tǒng)一為函數(shù) my_rust_lib,并在該函數(shù)中根據(jù) features 定義分別調(diào)用不同的函數(shù)并返回相應(yīng)的值。
最后是在 my-rust-bin 中編譯二進(jìn)制文件:
編譯并運行 v1 的二進(jìn)制文件
#編譯v1版本的my-rust-bin $cdmy-rust-bin $cargobuild--features="v1" #運行v1版本的my-rust-bin $target/debug/my-rust-bin my_rust_lib_v1:8 Thereturnvalueofmy_rust_libis[8]
編譯并運行 v2 的二進(jìn)制文件
#編譯v2版本的my-rust-bin $cdmy-rust-bin $cargobuild--features="v2" #運行v2版本的my-rust-bin $target/debug/my-rust-bin my_rust_lib_v2:8 Thereturnvalueofmy_rust_libis[8]
備注:如果編譯的時候沒有設(shè)置 --features 則會有如下輸出:
$cargobuild error:Pleasespecifyeither'v1'or'v2'feature -->src/main.rs1 | 16|compile_error!("Pleasespecifyeither'v1'or'v2'feature"); |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
至此,用戶在編譯好該基礎(chǔ)軟件之后,就可以無感知的通過統(tǒng)一的函數(shù)入口調(diào)用不同版本的相同鏈接庫中的不同函數(shù)了。
總結(jié)
本文主要是在簡化了問題的實際場景以后,解決不同版本的同一軟件包或者鏈接庫中,函數(shù)及其函數(shù)簽名不同導(dǎo)致的調(diào)用問題。之所以說簡化,主要是本文所描述的場景中,my-rust-bin 和其依賴的外部鏈接庫均是 Rust 編寫。而在我的實際場景中則會更復(fù)雜一些,存在著 Rust 代碼依賴 C 編寫的外部鏈接庫,同時存在混合的原來 C 代碼部分依賴新改寫的 Rust 外部鏈接庫的情況。但是無論哪種情況,萬變不離其宗,我們都可以從這種最簡單的場景出發(fā)去解決遇到的問題。
關(guān)于作者
張懷龍曾就職于阿爾卡特朗訊,百度,IBM等企業(yè)從事云計算研發(fā)相關(guān)的工作。目前就職于 Intel 中國,擔(dān)任云原生開發(fā)工程師并致力于云原生、服務(wù)網(wǎng)格等技術(shù)領(lǐng)域研究實踐,也是Istio 的maintainer的開發(fā)者。曾多次在 KubeCon、ServiceMeshCon、IstioCon、GOTC 和 InfoQ/QCon 等大會上發(fā)表演講。
審核編輯:湯梓紅
-
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7095瀏覽量
124965 -
C語言
+關(guān)注
關(guān)注
180文章
7630瀏覽量
140420 -
代碼
+關(guān)注
關(guān)注
30文章
4887瀏覽量
70264 -
Rust
+關(guān)注
關(guān)注
1文章
233瀏覽量
6962
原文標(biāo)題:一次Rust重寫基礎(chǔ)軟件的實踐(二)
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Rust GUI實踐之Rust-Qt模塊
循環(huán)充放電一次就是少一次壽命嗎?
一次電池為什么不能被充電?
循環(huán)充放電一次就是少一次壽命嗎?
電池循環(huán)充放電一次就是少一次壽命嗎?
微軟開發(fā)基于Rust的新編程語言,將很快開源
微軟正在研發(fā)基于Rust新的安全編程語言
Cloudflare用Rust重寫Nginx C模塊,構(gòu)建沒有Nginx的未來
Rust重寫的LSP:KCL IDE 插件的功能介紹與設(shè)計解析

Windows 11初嘗Rust,36000行內(nèi)核代碼已重寫!

一次調(diào)頻和二次調(diào)頻的概念 一次調(diào)頻可以實現(xiàn)無差調(diào)節(jié)?
Rust重寫基礎(chǔ)軟件的實踐代碼
JavaScript與Rust和WebAssembly集成

評論