Rustのcdylibのメモ。
|
|
Rustでは、システムライブラリーのファイル(Unixの*.so
、Windowsの*.dll
)を生成することが出来る。
これにより、他言語からRustの関数を呼ぶことが出来る。
システムライブラリーを生成するには、Cargo.tomlに以下の設定を入れる。
[lib] crate-type = ["cdylib"]
cdylibはC言語のdynamic library(動的ライブラリー)を意味しているそうだ。
Rust向けのライブラリーファイルも一緒に生成する場合は以下のように2つ指定する。
[lib] crate-type = ["rlib", "cdylib"]
他言語から呼べる関数を作ってみる。
#[no_mangle] pub extern "C" fn add(left: i32, right: i32) -> i32 { left + right }
「extern "C"
」を指定すると、C言語互換のABI(Application Binary
Interface)形式で関数がエクスポートされる。
#[no_mangle]
は、マングルしないようにする指定する属性。
マングルとは、コンパイルされた関数名(シンボル名)をRustに都合の良い形式にすること。
これでビルドを実行すると、システムライブラリーファイルが生成される。
(以下の例はWindowsなのでdllファイルが生成されている)
$ cargo build --release
$ ls -1 target/release/
build/
deps/
examples/
ffi_example_rust.d
ffi_example_rust.dll
ffi_example_rust.dll.exp
ffi_example_rust.dll.lib
ffi_example_rust.pdb
incremental/
libffi_example_rust.d
libffi_example_rust.rlib
→Javaから関数を呼び出す例
→C言語用のヘッダーファイルを生成するツール
構造体(のレイアウト)を他言語に公開する例。
#[repr(C)] pub struct MyStruct1 { value1: i32, value2: i32, }
#[repr(C)]
は、C言語形式の構造体を定義することを表す属性。
構造体を扱う関数は以下のような感じで定義できる。
// 構造体を返す関数 #[no_mangle] pub extern "C" fn create_my_struct1(value1: i32, value2: i32) -> MyStruct1 { MyStruct1 { value1, value2 } } // 構造体のポインターを受け取ってフィールドにセットする関数 #[no_mangle] pub extern "C" fn set_my_struct1(mystruct1: &mut MyStruct1, value1: i32, value2: i32) { mystruct1.value1 = value1; mystruct1.value2 = value2; }
引数に「&」を付けて参照にすると、C言語形式としてはポインターになるようだ。
構造体そのものを返す形(-> MyStruct1
)の場合、スタック内に構造体のメモリーが確保され、関数から返すときは、呼ばれた側で同じだけメモリーを確保し、そこに構造体のデータをコピーすると思われる。[2024-09-26]
なので、この方式の場合、返した構造体のメモリーはRust側では管理する必要が無い。
しかしそうなると、この構造体の方法は、フィールドをプリミティブ型だけにしないとまずそうな気がする。
(フィールドにポインターを保持している場合、そのポインターが指すメモリーは、関数の呼び出し元と呼び出された側のどちらで管理するかが曖昧になりそう)
構造体(のレイアウト)は、必ずしも他言語へ向けて公開する必要は無い。[2024-09-26]
構造体のポインターだけを他言語に渡し、構造体のフィールドを操作する関数を提供して、外部からは直接構造体の中を触らせないようにするのが良さそうだ。
pub struct MyStruct2 { int_value: i32, text_value: *mut c_char, string_value: Option<String>, }
今回の例では構造体のレイアウト(フィールド)は公開しないので、#[repr(C)]
を付ける必要は無い。
他言語へ返したい文字列をフィールドで持つ場合は、*c_charで保持するのが良いと思う。(今回の例のtext_value)
→text_valueのセッター/ゲッター
他言語へ渡す必要のないデータは、通常のRustのフィールドとして保持しても問題ないようだ。(今回の例のstring_value)
→string_valueのセッター
通常のRustデータを他言語へ見せようと思ったら、他言語へ渡すためのメモリーを確保する必要があり、渡すのに使ったメモリーの解放手段を考えなければならない。
なので、他言語へデータを渡すことを想定するのなら、最初からメモリーを確保して、そのポインターを保持しておく方がいいと思う。
構造体のメモリーを解放する為に解放用の関数は必ず呼んでもらわないといけないので、フィールドに保持しているポインターが指しているメモリーは、そのときに一緒に解放する。
#[no_mangle] pub extern "C" fn create_my_struct2() -> *mut MyStruct2 { let s2 = Box::new(MyStruct2 { int_value: 0, text_value: std::ptr::null_mut(), string_value: None, }); Box::into_raw(s2) }
MyStruct2インスタンスは通常のRust構造体通りに作成する。
そうすると、スタック上のメモリーが使用される。(関数から抜けると消えてしまう)
しかしこのインスタンスは関数の外へ返したいので、データが置かれる場所はスタックでなくヒープにする必要がある。
というわけでBoxを作り、そのポインター(アドレス)を関数から返している。
構造体のポインターを他言語へ渡したので、使い終わった後は、構造体のメモリーを解放する関数を必ず呼んでもらわないといけない。
#[no_mangle] pub extern "C" fn free_my_struct2(s2: *mut MyStruct2) { if s2.is_null() { return; } unsafe { let s2 = Box::from_raw(s2); // 構造体(の所有権)をポインターから獲得する if !s2.text_value.is_null() { let _ = CString::from_raw(s2.text_value); // CString(の所有権)を復元し、メモリーを解放する } } // スコープの終わりで所有権を持っているs2のメモリーが解放される。s2のフィールドstring_valueも一緒に解放される }
Boxからポインターに変換した関数はinto_raw()で、ポインターからBoxを復元するのはfrom_raw()。
文字列も、CStringからポインターに変換するメソッドはinto_raw()で、ポインターからCStringを復元するのはfrom_raw()。
分かりやすい。
#[no_mangle] pub extern "C" fn get_my_struct2_int_value(s2: *const MyStruct2) -> i32 { if s2.is_null() { return 0; } unsafe { (*s2).int_value } }
#[no_mangle] pub extern "C" fn set_my_struct2_int_value(s2: *mut MyStruct2, value: i32) { if s2.is_null() { return; } unsafe { (*s2).int_value = value; } }
ゲッターは値を返すだけ(構造体のフィールドを変更しない)なので、引数の構造体はconstでいい。
一方、セッターは構造体のフィールドに値をセットするので、受け取る構造体はmutにする必要がある。
*MyStrct2型の変数s2は、使うときに「(*s2)
」のようにアスタリスクを付ける。
また、このs2は外部から受け取ったポインターなので、それが指す内容(フィールド)にアクセスするときは、unsafeブロック内で行う必要がある。
(外部から受け取ったポインターはRustコンパイラーの管理外なので、Rustコンパイラーが安全性を保証できないから、unsafeということなのだろう)
#[no_mangle] pub extern "C" fn get_my_struct2_text_value(s2: *const MyStruct2) -> *const c_char { if s2.is_null() { return std::ptr::null(); } unsafe { (*s2).text_value } }
text_valueフィールドは*c_char型である。すなわちポインターを保持しているので、ゲッターからはそのポインターをそのまま返すことが出来る。
#[no_mangle] pub extern "C" fn set_my_struct2_text_value(s2: *mut MyStruct2, value: *const c_char) { if s2.is_null() { return; } unsafe { // 既に保持しているメモリーがあったら、解放する if !(*s2).text_value.is_null() { let _ = CString::from_raw((*s2).text_value); } let value = CStr::from_ptr(value); let value = CString::new(value.to_bytes()).unwrap(); (*s2).text_value = value.into_raw(); } }
text_valueのセッターの引数valueの型は*c_charで、フィールドの型も同じく*c_charである。
といことは、引数のポインターをそのままフィールドに格納するということも、やろうと思えば出来るだろう。
しかし、それはやらない方がいいと思う。
なぜなら、このセッターから戻った後に、ポインターが指しているメモリーが解放されてしまうかもしれないから。
そうなると、自分がフィールドに保持したポインターは無効な領域を指すことになり、使用できない。
(使用できなくなったことは分からないので、使用すると何が起こるか分からない。まぁクラッシュするか、本来の目的と違う値が読めてしまうか。いずれにしても正しく動作しない)
こういった事態を避けるためにも、フィールドに保持するのは自分で確保したメモリーのポインターにすべきだろう。
#[no_mangle] pub extern "C" fn set_my_struct2_string_value(s2: *mut MyStruct2, value: *const c_char) { if s2.is_null() { return; } unsafe { if value.is_null() { (*s2).string_value = None; return; } let value = CStr::from_ptr(value); let value = value.to_str().unwrap(); let value = String::from(value); (*s2).string_value = Some(value); } }
string_valueフィールドは通常のRustのOption<String>なので、引数の*c_charをStringに変換しさえすれば、後は普通のRustのプログラミング通りで扱える。
このstring_valueフィールドにはゲッターは用意していない。
Option<String>からCStringを生成して*c_charにして返すことは出来るだろうが、そのメモリーを解放する手段を提供しなければならないし、使う側もいちいち解放関数を呼ぶのは手間だろう(呼ぶのを忘れそうな気がする)から…。