如何在 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,安全性和隔離性:在某些情況下,出於安全原因,可能希望隔離應用程序的某些組件。動態庫可以幫助封裝敏感代碼,限制其對主應用程序的暴露。

動態庫加載的挑戰

動態庫加載也帶來了複雜性和潛在的挑戰。在使用動態庫時,需要考慮以下幾點:

總結

總而言之,當需要創建模塊化、靈活和可擴展的應用程序時,動態庫加載是最有益的,但它需要仔細設計、適當的資源管理。Rust 的安全機制可以幫助你避免與動態庫加載相關的許多陷阱,但是仍然需要意識到所涉及的風險和挑戰。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GKTql1VWwtsfGGdCstfR4g