如何在 Rust 中加載動態庫?
動態庫加載,也稱爲動態加載或運行時動態鏈接,是一種編程技術,它允許程序將外部庫 (也稱爲動態鏈接庫或共享庫) 加載到內存中,並在運行時而不是編譯時使用它們的功能。這些庫包含可由程序執行的編譯代碼,從而支持模塊化、代碼重用和靈活性。
動態庫加載與靜態鏈接相反,在靜態鏈接中,在編譯時,所有必要的代碼都包含在程序的可執行二進制文件中。通過動態庫加載,程序的大小保持較小,並且庫可以在不需要更改主程序的情況下更新或替換。
Rust 中的動態庫加載
Rust 爲在運行時加載動態庫提供了一個安全且符合人體工程學的接口。libloading crate 提供了一個跨平臺的 API,用於加載動態庫和調用這些庫中定義的函數。它還爲類 unix 系統上的 dlopen 和 dlsym 函數以及 Windows 上的 LoadLibrary 和 GetProcAddress 函數提供了一個安全的封裝。
設置工作空間
讓我們從創建一個新的 Rust 項目 lib-loading 開始:
cargo new lib-loading
刪除 SRC 目錄,因爲我們將創建一個工作空間。
現在,更新 Cargo.toml 文件創建一個工作區並添加兩個成員:
[workspace]
members = [
"hello-world",
"executor",
]
創建一個動態庫
現在讓我們在項目根目錄下創建一個名爲 hello-world 的庫:
cargo new hello-world --lib
Cargo.toml 文件如下:
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["dylib"]
注意 [lib] 部分中的 crate-type 字段。這告訴編譯器構建一個動態庫,而不是默認的靜態庫。這個輸出類型將在 Linux 上創建 *.so 文件,在 macOS 上是 *.dylib 文件,在 Windows 上是 *.dll 文件。
接下來,更新 src/lib.rs 文件,定義一個名爲 execute 的函數,該函數將消息打印到控制檯:
#[no_mangle]
pub fn execute() {
println!("Hello, world!")
}
名稱混淆是一種編譯器技術,它用函數的參數和返回類型等附加信息對函數名進行編碼。然而,這使得從其他語言或動態加載的庫調用函數變得困難。
no_mangle 屬性關閉 Rust 的名稱混淆,這樣它就有一個定義良好的符號來鏈接。允許我們從動態加載的庫中調用函數。
創建一個可執行二進制文件
接下來,讓我們在項目根目錄下創建一個名爲 executor 的 crate,它將加載 hello-world 庫並調用其中定義的 execute 函數。
cargo new executor
在 Cargo.toml 文件中添加一個對 libloading crate 的依賴:
[dependencies]
libloading = "0.8"
現在,更新 src/main.rs 文件加載 hello-world 庫並調用 execute 函數:
use libloading::{Library, library_filename, Symbol};
fn main() {
unsafe {
// 加載“hello_world”庫
let lib = Library::new(library_filename("hello_world")).unwrap();
// 獲取函數指針
let func: Symbol<fn()> = lib.get(b"execute").unwrap();
func() // 調用函數
}
}
使用 library_filename 函數獲取庫的特定於平臺的文件名是一種很好的做法。
構建並運行項目
首先,讓我們使用 cargo build 構建項目:
cargo build --release
cargo run -p executor --release
Finished release [optimized] target(s) in 0.03s
Running `target/release/executor`
Hello, world!
恭喜你!你已經成功加載了一個動態庫,並調用了其中定義的函數。
何時使用動態庫加載?
希望在應用程序中實現模塊化、靈活性和運行時可擴展性的幾個場景中,動態庫加載非常有用。下面是一些動態庫加載可以發揮優勢的情況:
1,插件系統:如果你正在構建一個支持插件或擴展的應用程序,動態庫加載可以讓你在運行時加載和卸載這些插件,而無需修改或重新啓動主應用程序。這對於支持第三方插件的文本編輯器、web 瀏覽器或媒體播放器等應用程序非常有用。
2,模塊化應用程序:在開發大型應用程序時,動態庫加載可以幫助你將功能分解爲更小的、可管理的組件。這可以導致更快的開發週期、更容易維護和改進代碼組織。
3,熱加載:對於某些類型的應用程序,如遊戲開發工具或圖形設計軟件,動態庫加載可以實現熱加載。這意味着可以只修改應用程序的部分代碼,將它們編譯成動態庫,然後加載該庫,而無需重新啓動整個應用程序,這大大加快了開發迭代。
4,特定於平臺的實現:如果應用程序需要與特定於平臺的特性交互,動態庫加載允許爲不同的平臺提供不同的實現。可以根據運行時環境加載適當的庫,避免在主應用程序的二進制文件中出現不必要的代碼。
5,版本控制和更新:當希望爲應用程序的特定部分提供更新或錯誤修復時,可以只分發和加載更新的動態庫,而不需要用戶更新整個應用程序。
6,資源共享:動態庫可用於跨應用程序的多個部分共享資源,如數據庫連接、網絡套接字或其他系統資源。這有助於優化資源使用並提高性能。
7,語言互操作性:如果需要與用其他編程語言編寫的代碼 (例如,C 或 C++) 進行交互,動態庫提供了一種從 Rust 代碼中調用用這些語言編寫的函數的方法。
8,安全性和隔離性:在某些情況下,出於安全原因,可能希望隔離應用程序的某些組件。動態庫可以幫助封裝敏感代碼,限制其對主應用程序的暴露。
動態庫加載的挑戰
動態庫加載也帶來了複雜性和潛在的挑戰。在使用動態庫時,需要考慮以下幾點:
-
內存管理:需要正確地管理內存和資源分配,確保在不再需要資源時釋放資源並卸載庫,以避免內存泄漏。
-
兼容性:確保動態加載的庫與應用程序的版本和其他庫兼容可能是一項挑戰。版本不匹配可能導致崩潰或意外行爲。
-
unsafe 代碼:處理動態加載的庫經常涉及使用 unsafe 關鍵字,因爲 Rust 的安全機制無法完全驗證與運行時操作相關的固有風險。
-
調試:與單應用程序相比,在動態加載的庫中調試問題可能更具挑戰性。
-
性能開銷:與將代碼直接靜態鏈接到應用程序二進制文件中相比,使用動態庫可能會有輕微的性能開銷。
總結
總而言之,當需要創建模塊化、靈活和可擴展的應用程序時,動態庫加載是最有益的,但它需要仔細設計、適當的資源管理。Rust 的安全機制可以幫助你避免與動態庫加載相關的許多陷阱,但是仍然需要意識到所涉及的風險和挑戰。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/GKTql1VWwtsfGGdCstfR4g