R&D チームの奥村(@izariuo440)です。共有ライブラリを動的読み込み(動的リンクではない)できると、プラグインやアドオンによってアプリケーションを拡張可能にできて便利です。今回は、rust-bindgen を使って Rust 製アプリケーションから C API を持つ共有ライブラリを手軽に動的読み込みする方法のメモです。まとめると以下のとおりです。
- rust-bindgen は、v0.58.0 から共有ライブラリの動的読み込みコードを生成できる
- 共有ライブラリの動的読み込みコードを生成するには、
--dynamic-loading
オプションを使う - 共有ライブラリを動的読み込み直後にシンボルを確認するには、
--dynamic-link-require-all
オプションを使う - これらはドキュメントには記載されていないが、
bindgen
コマンドのヘルプを見ると記載がある
利用方法
まず cargo install bindgen
で bindgen
をインストールしておいてください。
以下のようなヘッダファイル foo.h
を用意して試します。
#ifndef FOO_H #define FOO_H #ifdef __cplusplus extern "C" { #endif void foo_open(); void foo_close(); #ifdef __cplusplus } #endif #endif
共有ライブラリを動的読み込みしないパターン
以下を実行すると・・・
bindgen foo.h > bindings.rs
bindings.rs
には以下が出力されました。シンプルですね。
/* automatically generated by rust-bindgen 0.58.1 */ extern "C" { pub fn foo_open(); } extern "C" { pub fn foo_close(); }
共有ライブラリを動的読み込みするパターン
--dynamic-loading
オプションを追加して、以下を実行すると・・・
bindgen --dynamic-loading foo foo.h > dynamic-bindings.rs
dynamic-bindings.rs
には以下が出力されます。かなりコードが増えました。libloading クレートを使って、共有ライブラリを動的読み込みするコードなどが追加されています。実際に使うときは foo::new()
または foo::from_library()
を使います。ただし、共有ライブラリが提供するシンボルの一部がない場合は panic が発生するので注意が必要です。
/* automatically generated by rust-bindgen 0.58.1 */ extern crate libloading; pub struct foo { __library: ::libloading::Library, pub foo_open: Result<unsafe extern "C" fn(), ::libloading::Error>, pub foo_close: Result<unsafe extern "C" fn(), ::libloading::Error>, } impl foo { pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error> where P: AsRef<::std::ffi::OsStr>, { let library = ::libloading::Library::new(path)?; Self::from_library(library) } pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error> where L: Into<::libloading::Library>, { let __library = library.into(); let foo_open = __library.get(b"foo_open\0").map(|sym| *sym); let foo_close = __library.get(b"foo_close\0").map(|sym| *sym); Ok(foo { __library, foo_open, foo_close, }) } pub unsafe fn foo_open(&self) -> () { (self .foo_open .as_ref() .expect("Expected function, got error."))() } pub unsafe fn foo_close(&self) -> () { (self .foo_close .as_ref() .expect("Expected function, got error."))() } }
共有ライブラリを動的読み込みするパターン その2
さらに --dynamic-link-require-all
オプションを追加して、以下を実行すると・・・
bindgen --dynamic-link-require-all --dynamic-loading foo foo.h > dynamic-all-bindings.rs
dynamic-all-bindings.rs
には以下が出力されます。from_library()
の中でシンボル取得の後ろに ?
がつき、存在しない場合はエラーになるようにコードが変わりました。その結果、関数呼び出し時に panic しなくなっています。
/* automatically generated by rust-bindgen 0.58.1 */ extern crate libloading; pub struct foo { __library: ::libloading::Library, pub foo_open: unsafe extern "C" fn(), pub foo_close: unsafe extern "C" fn(), } impl foo { pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error> where P: AsRef<::std::ffi::OsStr>, { let library = ::libloading::Library::new(path)?; Self::from_library(library) } pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error> where L: Into<::libloading::Library>, { let __library = library.into(); let foo_open = __library.get(b"foo_open\0").map(|sym| *sym)?; let foo_close = __library.get(b"foo_close\0").map(|sym| *sym)?; Ok(foo { __library, foo_open, foo_close, }) } pub unsafe fn foo_open(&self) -> () { (self.foo_open)() } pub unsafe fn foo_close(&self) -> () { (self.foo_close)() } }
利用できるバージョン
2020/11/25 の以下のコミットで対応が入ったようです。
CHANGELOG を見ると、0.56.0 や 0.58.0 に記載があります。
0.56.0: Experimental dynamic library support via dynamic_library_name (#1846). 0.58.0: Add from_library for generated dynamic library structs [#2011][].
v0.55.1 からの変更点を見ると、v0.58.0 までには関連する変更が入っています。その後は今のところないようです。
まだドキュメントはないようです。
さいごに
オプティムでは、こうした技術に興味がある・作ってみたい・既に作っている、というエンジニアを募集しています。興味のある方は、こちらをご覧ください。