S-JIS[2024-10-15] 変更履歴

Rust libloadingメモ

Rustのlibloadingクレートのメモ。


概要

libloadingは、Rustで外部ライブラリーを動的にロードして関数を呼び出せるクレート(ライブラリー)。

extern "C"によって外部関数を定義すると、その関数が入っている外部ライブラリーがコンパイル時にも必要だし、実行時にも環境変数LD_LIBRARY_PATHに外部ライブラリー(soファイル)の場所を指定する必要がある。
libloadingを使えば、コンパイル時の読み込みは不要だし、実行時に読むsoファイルの場所をプラグラム内で指定することが出来る。


Cargo.toml:

〜

[dependencies]
libloading = "0.8.5"

libloadingの例

extern "C"の例と同等 のものをlibloadingを使って書いてみる。


まず、libloading::Libraryを使って、外部ライブラリーファイル(soファイル)を読み込む。

use std::{
    path::Path,
    sync::Once,
};

use libloading::Library;
static LIBRARY_INIT: Once = Once::new();
static mut LIBRARY: Option<Library> = None;

fn get_library() -> &'static Library {
    unsafe {
        LIBRARY_INIT.call_once(|| {
            let filename = Path::new("/path/to/lib_dir/libexample.so");
            let lib = Library::new(filename).expect(&format!("{:?} load error", filename));
            LIBRARY = Some(lib)
        });
        LIBRARY.as_ref().unwrap()
    }
}

この後にLibraryインスタンスを使ってシンボル(関数名)を取得するのだが、シンボルが有効な間はLibraryインスタンスも生きていないと駄目なので、staticな変数にLibraryインスタンスを保持することにする。
(static変数用の初期値を生成する処理を書くためにOnceを使用する)


次に、Libraryインスタンスを使ってシンボル(関数名)を取得する。

取得したシンボルは何回も使う(毎回取得したくない)ので、(Libraryインスタンスの保持と同様に)static変数で保持しておくことにする。

use std::ffi::c_char;

use libloading::Symbol;
#[repr(C)]
struct Int64Result {
    value: i64,                  	// int64_t
    error_message: *const c_char,	// const char*
}

//「extern "C"」の代わりに関数のシンボルを保持する構造体
struct NativeFunctions {
    // Int64Result createContainer(const char* name);
    create_container: Symbol<'static, unsafe extern "C" fn(name: *const c_char) -> Int64Result>,

    // void closeContainer(int64_t handle);
    close_container: Symbol<'static, unsafe extern "C" fn(handle: i64)>,
}

libloading::Symbolを使って関数のシンボルを保持する。
Symbolの型引数で、関数の定義を記述する。

static INIT: Once = Once::new();
static mut NATIVE_FUNCTIONS: Option<NativeFunctions> = None;

fn native_functions() -> &'static NativeFunctions {
    unsafe {
        INIT.call_once(|| {
            let lib = get_library();
            let native_functions = NativeFunctions {
                create_container: lib.get(b"createContainer").expect("createContainer() not found"),
                close_container: lib.get(b"closeContainer").expect("closeContainer() not found"),
            };
            NATIVE_FUNCTIONS = Some(native_functions);
        });
        NATIVE_FUNCTIONS.as_ref().unwrap()
    }
}

libloading::Libraryのgetメソッドを使って、関数名のシンボルを取得する。


今回の例では、createContainer()を呼んだ後は最後に必ずcloseContainer()を呼ぶ必要があるので、専用の構造体を作ってDropトレイトを実装しておく。

use std::ffi::{CStr, CString};
pub(crate) struct ExternContainer {
    handle: i64,
}
impl ExternContainer {
    pub(crate) fn new(name: &str) -> Result<ExternContainer, String> {
        let name = CString::new(name).unwrap();

        let result = unsafe { (native_functions().create_container)(name.as_ptr()) };
        if !result.error_message.is_null() {
            let error_message = unsafe { CStr::from_ptr(result.error_message) };
            return Err(format!("{}", error_message.to_str().unwrap()));
        }

        let container = ExternContainer {
            handle: result.value,
        };
        Ok(container)
    }
}
impl Drop for ExternContainer {
    fn drop(&mut self) {
        unsafe {
            (native_functions().close_container)(self.handle);
        }
    }
}

Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま