Gtk+のRustラッパーであるgtk-rsの入門を書いた:RustとGtk+で開発するGUIアプリケーション
ほんとはもう少しやるといいことがあるのだけど(GResource、スレッド対応など)、まぁとりあえずここまで書いてあればGUIアプリケーション書いたことある人であれば感じは掴めるのかなと。ということでRev0.1。
(少なくともいまのところは)ニーズはほとんどないと思うけど、やる気があるうちにある程度形にしておかないと絶対書かないので。自分がこれから書いていくアプリケーションを読み進めるにあたっての基礎知識となれば。
2016-04-01
Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer)
Rustは C FFI が強いので、はっきりしたstructとCの関数を呼び出すなんていうケースでは、それっぽいstructとexternでの関数の定義を書けば呼び出せる。コールバックもできる。
→他言語関数インターフェイス
しかし、以下のようなケースではちょっとやりにくい。
自分が必要だったところがC世界からのコールバック呼び出しだったので、それについても触れる。基本的には、RustのアプリケーションがCライブラリを使うという想定。コールバックなので、行ったり来たりなのだけどね。
上記がsrc/bridge.cに置かれているとする。
まず、Cargo.tomlにビルドスクリプトが「build.rs」にあるよ、ということを指示する。またコンパイラの実行の面倒を見てくれる「gcc」crateをbuild dependenciesに入れる(gccと言っているが、MSVCも対応しているらしい。試してないが)。
dependenciesのlibcは、ビルドスクリプト的には必要ないが、C FFIではほぼ使うのでここで入れておく。
上記で定義した「build.rs」に、コンパイラの呼び出しを定義する。
このあたりの記述、詳しくはCargoのドキュメント「Build Script Support」を参照。
本来ライブラリのリンクの指示も必要だが、ここでは一旦省く。詳しくは上記ドキュメントを参照(下でもすこし触れる)。
Cargoの流儀的には、FFIのためのRustらしくない定義はなんちゃら-sysというcrateに定義するほうがそれらしい。この部分だけの差し替えがしやすいから、とのこと。
必ずしもこうでなくてよいが、こうしておくことで「内側」をBox(ヒープ)で持つことが出来、ポインタに変換してC関数のポインタに渡すことができる。
git2-rsのsrc/transport.rsでやっていたので参考にした。
→他言語関数インターフェイス
しかし、以下のようなケースではちょっとやりにくい。
- 必要な関数の呼び出しに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これでだいたい使えるはず。
おまけ:pkg-configでライブラリを参照させる
本来ラップする対象があるはずなので、リンク対象やincludeパスを指定する必要がある。基本的には、println!()で、cargoが必要な情報を渡してあげればよいが、build-dependenciesでpkg-config crateを使うと、pkg-configの結果を使える。例えばこんな感じ。 with-glib branchで使ってみてある。実際のところは、panicはやっつけなのでちゃんと分岐させたり、pkg-configに関わらず環境変数で設定できるようにしたりと、工夫がされていることが多いので、詳しくはそこらのライブラリのコードを参照。例えば、glib-sys crateはこんな感じ。2015-06-13
RustとWindowsのワイド文字列(LPCWSTR / LPWSTR)
最近?のWindowsでC FFIで文字列を受渡しをするとなると、ほぼ確実にでてくるのがワイド文字列(「Unicode」文字列)。それをRustでどう取り扱うかについて。
最初に感想を書いておくと、今のところ、Rustでのwchar_t *なNULL終端された「UTF-16」文字列の取り扱いは、char *なNULL終端されたUTF-8文字列に比べるとあまり親切な取扱いではない。しかし、そこまで大変でもなかった。たぶんUTF-8じゃないマルチバイト文字列を扱うよりラク。
コードはこちら。
「from_wide_ptr」は、from_wide()が&[u16]を受け取るので、スライスを作っていて、スライスを作る大きさを\0までの長さで計算している。
「to_wide_chars」は、encode_wide()でIteratorを得て、NULLだけを返すIteratorをSome(0).into_iter()で作ってchain()でくっついたIteratorを生成し、collect()でVecにしている。
一応呼び出し側のサンプルはこちら→https://gist.github.com/sunnyone/1d9e081df7eada21911d
(コンパイル方法はRustでDLLを作るの記事を読んでね)
「from_wide_ptr」は、効率が悪いと思いつつ利便性は一番これがいいかなと思ってto_string_lossy()をownedにした(コピーした)Stringを返しているけども、これがいつでも最適解だとは思っていない。OsStringからはlosslessにencode_wide()できると言っているので、ただ引き回すだけならOsStringそのままがいいかもしれないし、厳密性が大事なやつは不正なバイト列があったとき(たぶんペアになってないサロゲートペアとか)に変換に失敗するinto_string()のほうがいいかもしれない。取り扱っているコード次第だと思う。
最初に感想を書いておくと、今のところ、Rustでのwchar_t *なNULL終端された「UTF-16」文字列の取り扱いは、char *なNULL終端されたUTF-8文字列に比べるとあまり親切な取扱いではない。しかし、そこまで大変でもなかった。たぶんUTF-8じゃないマルチバイト文字列を扱うよりラク。
前提知識
コードを読む前の前提の話について、箇条書きで書くとこんな感じ:- Rustのふつうの文字列は、ざっくり言うとimmutableなstrと、mutableなString。
- std::str/Stringは、UTF-8文字列の型。NULL終端ではない。
- std::ffi::CStr/CStringは、UTF-8なchar *(NULL終端あり)をRustの世界で扱えるようにするラッパーの型。コピーが起きにくいようになっている。
- std::ffi::OsStr/OsStringは、Windowsのワイド文字列も扱えるようにした文字列の型(NULL終端でない)
- unixとwindowsのそれぞれにOsStrExt / OsStringExt traitがあり、OsStr/OsStringの出入りに使える。
- Windowsは、std::os::windows::ffi::OsStrExt と std::os::windows::ffi::OsStringExt (ソースを見るに1.0 stableなのにdoc.rust-lang.orgにない...)
変換関数
と、いうことで、便利なCStr/CStringは使えないものの、OsStr/OsStringを経由させればOK。ただし、NULL終端あり/なしの変換にちょっと小細工がいる、という感じ。コードはこちら。
「from_wide_ptr」は、from_wide()が&[u16]を受け取るので、スライスを作っていて、スライスを作る大きさを\0までの長さで計算している。
「to_wide_chars」は、encode_wide()でIteratorを得て、NULLだけを返すIteratorをSome(0).into_iter()で作ってchain()でくっついたIteratorを生成し、collect()でVecにしている。
一応呼び出し側のサンプルはこちら→https://gist.github.com/sunnyone/1d9e081df7eada21911d
(コンパイル方法はRustでDLLを作るの記事を読んでね)
注意書き
trait作ってprimitiveにimplしたほうがいいかもしれない。そこは実際に自分が使うときにやるかも(真面目にOsStringExtに実装してpull reqしたら取り込んでもらえる?プロセスの堅さどうなんだろ...)「from_wide_ptr」は、効率が悪いと思いつつ利便性は一番これがいいかなと思ってto_string_lossy()をownedにした(コピーした)Stringを返しているけども、これがいつでも最適解だとは思っていない。OsStringからはlosslessにencode_wide()できると言っているので、ただ引き回すだけならOsStringそのままがいいかもしれないし、厳密性が大事なやつは不正なバイト列があったとき(たぶんペアになってないサロゲートペアとか)に変換に失敗するinto_string()のほうがいいかもしれない。取り扱っているコード次第だと思う。
2015-06-07
RustでDLLを作る
期待通りの方法で作れた。ちなみに:
Cargo.tomlを書き換えて、[lib]を作る。詳しくはhttp://doc.crates.io/manifest.htmlを参照。
lib.rsに、no_mangleとextern "C"をつけて、関数を記述する。
詳しくは、Rust bookの「Foreign Function Interface」を参照。
あとは、
Dependency Walkerで見てみると、いろいろな関数がexportされているが、その中で指定したhelloがexportされていることがわかる。
結果はこう:
問題ない。boolってなによと思ってintにしてみたら、0と1らしい。
c:\>rustc --version rustc 1.2.0-nightly (613e57b44 2015-06-01) (built 2015-06-02)今回のは特にfeature使ってないから1.0でいけるかな?
DLLの作り方
まずプロジェクトを作る。cargo new rustdlltest
Cargo.tomlを書き換えて、[lib]を作る。詳しくはhttp://doc.crates.io/manifest.htmlを参照。
lib.rsに、no_mangleとextern "C"をつけて、関数を記述する。
詳しくは、Rust bookの「Foreign Function Interface」を参照。
あとは、
cargo buildか
cargo build --releaseで、target\debugあるいはtarget\releaseにfoo.dllができている。
Dependency Walkerで見てみると、いろいろな関数がexportされているが、その中で指定したhelloがexportされていることがわかる。
呼んでみる
楽なのでC#から呼んでみるには、こんな感じ。結果はこう:
>RustLibSample.exe A negative count is not supported. Result for -1: False Hello, World: 0 Hello, World: 1 Hello, World: 2 Result for 3: True
問題ない。boolってなによと思ってintにしてみたら、0と1らしい。
2015-05-17
「RustからPythonを呼び出す」資料と補足
Rust 1.0 Release記念祝賀LT会 お疲れ様でした。そして1.0.0リリースおめでとうございます。今回は、その祝賀会で「RustからPythonを呼び出す」という話をしたので、その補足エントリです(資料は最下部に配置)。
(恐らく非Windows/x64 or Windows/x86,x64なら:資料参照)(2015/5/27追記:下部追記参照)
(Cargo.tomlを編集)
(src/main.rsを作成)
ただ、GCがついてくるので、そのへん意識しないといけないですね。
とこんなかんじですが、他にもおすすめがあれば教えてください。
. o O (もうちょっとうまいこと話せたらよかったのだけど、スタートがうまくしゃべれなかったせいでぐだぐだになってしまった...)
[2015/5/27 追記]
資料にあるLinux/x86でビルドできない罠ですが、Linux/x86対応をpull request出したらマージされたので、Linux/x86でもgitのmasterはビルドできるようになりました。Cargo.tomlもgitのほうを向ければビルドできるようになると思います。
rust-cpythonの使い方
RustからPythonを呼び出すには、rust-cpython crate が使えます。使い方は、githubに書いてあるとおりですが、以下のような感じで簡単に使えます$ cargo new newapp $ cd newapp
(Cargo.tomlを編集)
[dependencies.cpython] version = "*"
(src/main.rsを作成)
extern crate cpython; use cpython::{PythonObject, Python}; fn main() { let gil_guard = Python::acquire_gil(); let py = gil_guard.python(); let sys = py.import("sys").unwrap(); let version = sys.get("version").unwrap().extract::().unwrap(); println!("Hello Python {}", version); }
$ cargo run
作りたかったもの
なぜこのような話になったかというと、DLLを作成することで拡張できるアプリケーションがあるのですが、その拡張する内容自体、LLでちょいちょい変えていきたいので、LLとつなぐブリッジとなるDLLを作りたかった(作りたい)のです。Pythonなのは、アプリ埋め込みがしやすく、統計処理が得意なライブラリを持っているから。あと、coroutineが使いたいので、Python 3.4以降が使いたいです(ここはRustでもまだできていないところですが、python3-sysがあるので、どっか切り替えればなんとかなるんじゃないかと思っているところ)考えた言語たち
Rustを使いたいからというよりも、Rustが向いているのではと思ったからです。以下、考えた言語たち。C#
今回はWindowsの世界だとわかりきっているので、私的にはC#で書けると一番楽です。しかし、C#のDLLはmanaged (CLRで動作する) DLLなので、普通のC関数として呼び出すことはできません。一応、その向きのために「Unmanaged Exports」というのがあるようですが、コンパイルできませんでした。多分「Can't create unmanaged dll using C# and Robert Giesecke's Unmanaged Exports tool」という記事にある問題だと思うのですが、試してません。動いても動かすにはCLRがついてくるはずなので、フットプリントが大きくなり、単なるPythonブリッジにはちょっと大きいという点が気になります。C
いろいろなもののベースになっているはずなので、Cに限った話ではないのですが、PythonのC APIは親切に提供されているので、Cで書くのも悪くないと思います。ないがしろになりがちそうなC拡張のドキュメントがしっかり書かれていて、和訳もあるという親切な環境です。悪くないといえば悪くないのですが、リソース管理系の話だとか、objectiveな操作が書きにくいとか、ヘッダファイルが別で冗長とか、いろいろあるので、他にベターなものがあれば避けたいなと。D
Win32 DLLs in Dという話も書いてあるし、これは結構ありかなと思っています。pydというPython bindingもあるみたいです。CPython 2.6+とあるので、2系しか考えてない気がしますが、Python 3系いけるかどうか。ただ、GCがついてくるので、そのへん意識しないといけないですね。
Free Pascal (Object Pascal)
どこかでこれでDLLを書いてる例を見かけて、意外とありなのかなと思ったりしたりしました。Python4DelphiというPythonバインディングもあるようです。今からPascal覚えるのかーというところですね。Rust
ここでRustを思い出して、よさそうだなと。#![create_type = "dylib"]と#[no_mangle]でDLLは作れそうだし、GCがないのでランタイムのオーバーヘッドも小さそうだし。そしてrust-cpythonを動かしてみたら、動いた ← 昼間ココ というわけです。Pythonそのもの
呼び出そうと思っているのはPythonそのものなので、DLLがPythonで書けるとベターです。できないと思っていたのですが、Cythonを使うのはどうという話があり、こっちのほうがいいかも? ← 今ココ です。Pythonではないけど、専用のサポートがあるし、読む人もなじみやすそうですね。とこんなかんじですが、他にもおすすめがあれば教えてください。
資料
ともあれ、資料はこちらになります。. o O (もうちょっとうまいこと話せたらよかったのだけど、スタートがうまくしゃべれなかったせいでぐだぐだになってしまった...)
[2015/5/27 追記]
資料にあるLinux/x86でビルドできない罠ですが、Linux/x86対応をpull request出したらマージされたので、Linux/x86でもgitのmasterはビルドできるようになりました。Cargo.tomlもgitのほうを向ければビルドできるようになると思います。
登録:
投稿 (Atom)