rust-bindgen で手軽に共有ライブラリを動的読み込み

R&D チームの奥村(@izariuo440)です。共有ライブラリを動的読み込み(動的リンクではない)できると、プラグインやアドオンによってアプリケーションを拡張可能にできて便利です。今回は、rust-bindgen を使って Rust 製アプリケーションから C API を持つ共有ライブラリを手軽に動的読み込みする方法のメモです。まとめると以下のとおりです。

  • rust-bindgen は、v0.58.0 から共有ライブラリの動的読み込みコードを生成できる
  • 共有ライブラリの動的読み込みコードを生成するには、--dynamic-loading オプションを使う
  • 共有ライブラリを動的読み込み直後にシンボルを確認するには、--dynamic-link-require-all オプションを使う
  • これらはドキュメントには記載されていないが、bindgen コマンドのヘルプを見ると記載がある

利用方法

まず cargo install bindgenbindgen をインストールしておいてください。

以下のようなヘッダファイル 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 までには関連する変更が入っています。その後は今のところないようです。

まだドキュメントはないようです。

さいごに

オプティムでは、こうした技術に興味がある・作ってみたい・既に作っている、というエンジニアを募集しています。興味のある方は、こちらをご覧ください。

www.optim.co.jp