→他言語関数インターフェイス
しかし、以下のようなケースではちょっとやりにくい。
- 必要な関数の呼び出しにCマクロが必要
- opaqueなstructの下のほうのメンバにアクセスが必要
自分が必要だったところがC世界からのコールバック呼び出しだったので、それについても触れる。基本的には、RustのアプリケーションがCライブラリを使うという想定。コールバックなので、行ったり来たりなのだけどね。
やりかたの概要
やりかたは概ね以下の通り。- ラッパー関数をCで用意する
- CargoのビルドスクリプトでCソースのコンパイル方法を指示する
- ラッパー関数をexternで定義する
- ラッパー関数を呼び出すstruct & traitを定義する
- 実際に呼び出す
やりかた
ラッパー関数の準備
Rustから呼び出したい関数を、普通の関数呼び出しにできる形で定義する。本来は何かのコードのラッパーの想定なのだが、そっち側の知識が必要となると話がややこしくなるので、ここでは単なるCソースを例として使う。上記がsrc/bridge.cに置かれているとする。
Cargo.toml + build.rsにCソースのコンパイル方法の指示する
ここがこの記事のキモ。cargoに、置いたCソースのコンパイル方法を指示する必要がある。まず、Cargo.tomlにビルドスクリプトが「build.rs」にあるよ、ということを指示する。またコンパイラの実行の面倒を見てくれる「gcc」crateをbuild dependenciesに入れる(gccと言っているが、MSVCも対応しているらしい。試してないが)。
[package] (略) build = "build.rs" [build-dependencies] gcc = "0.3" [dependencies] libc = "0.2"
dependenciesのlibcは、ビルドスクリプト的には必要ないが、C FFIではほぼ使うのでここで入れておく。
上記で定義した「build.rs」に、コンパイラの呼び出しを定義する。
extern crate gcc; fn main() { gcc::compile_library("libbridgesample.a", &["src/bridge.c"]); }
このあたりの記述、詳しくはCargoのドキュメント「Build Script Support」を参照。
本来ライブラリのリンクの指示も必要だが、ここでは一旦省く。詳しくは上記ドキュメントを参照(下でもすこし触れる)。
ラッパー関数をexternで定義する(いわゆる-sys部分)
ここからはもうRust onlyでのFFIと一緒。externでfnとstructを定義する。opaqueなstructをenumを定義するのがイディオムだそうだ。Cargoの流儀的には、FFIのためのRustらしくない定義はなんちゃら-sysというcrateに定義するほうがそれらしい。この部分だけの差し替えがしやすいから、とのこと。
ラッパー用のstructを用意する
ffi層を叩く、Rust 的構造にあわせた呼び出し部を用意する。今回は、コールバックをクロージャでもらい、それを実際のCコールバックから呼ぶ形にした。「外側」と「内側」の2つのstructを作る
Rust世界のコード用のデータを入れておく「内側」と、その「内側」とC関数からもらったポインタを格納しておく「外側」を用意する。必ずしもこうでなくてよいが、こうしておくことで「内側」をBox(ヒープ)で持つことが出来、ポインタに変換してC関数のポインタに渡すことができる。
git2-rsのsrc/transport.rsでやっていたので参考にした。
Drop traitを実装しポインタが自動で捨てられるようにする
後始末大事。コールバックとしてC側に渡す関数をexternで定義する
今回はライフタイムをRustが持っているので、キャストするだけ。ここでは、Rust側がライフタイムを握っているのでポインタを渡しているが、もしC側がライフタイムを握るようであれば、Box::into_rawとBox::from_rawをうまく使うといいと思う。ライフタイムの制御から外せる。mem::forget というのもあるかな。ラッパーを実装
あとはffi関数を呼び出すようMyStructにimplする。呼び出しコードを書く
実際呼ぶコードはこんなかんじになる。 結果はこんな感じ。mystruct_new Hello, World: 11 Hello Result: 12345 mystruct_freeこれでだいたい使えるはず。
0 件のコメント:
コメントを投稿