6-1. RustからCを呼ぶ
ここでは、RustのソースコードからC言語のソースコードを呼び出す方法を説明します。やることは2つです。
- CのAPIを、Rustで使えるようにインタフェースを定義する
- Cのコードを、Rustのコードと一緒にビルドする
上記の1.については、bindgenで自動生成できます。2.については、基本的に、Rustのbuild.rs
スクリプトで対応します。
インタフェース定義
まず、手動でインタフェースを定義する例を示します。標準ライブラリが使える環境と異なる点は、cty
クレートを使う点です。
今、次のようなCヘッダファイルが公開されているとします。
/* target.h */
typedef struct MyStruct {
int32_t x;
int32_t y;
} MyStruct;
void my_function(int32_t i, MyStruct* ms);
このヘッダファイルをRustに変換すると、インタフェースは次のようになります。
/* bindings.rs */
#[repr(C)]
pub struct MyStruct {
pub x: cty::int32_t,
pub y: cty::int32_t,
}
pub extern "C" fn my_function(
i: cty::int32_t,
ms: *mut MyStruct
);
#[repr(C)]
アトリビュートにより、Rustコンパイラは、構造体のデータをCと同じルールで構成します。デフォルトでは、Rustコンパイラは、struct
内のデータ順やサイズを保証しません。
pub extern "C" fn my_function(...);
このコードは、my_function
という名前の、C ABIを使った関数を宣言します。関数の定義は、別の場所で与えるか、静的ライブラリから最終バイナリにリンクする必要があります。
インタフェースの自動生成
上述の通り、手動でインタフェースを作成することもできますが、これは単純作業です。そこで、インタフェースを自動生成してくれるbindgen
を利用します。ここでは、一般的な手順を説明し、具体的な例は、ケーススタディ Zephyr bindingsで紹介します。
標準ライブラリが使える環境と、ベアメタル環境とで共通する手順は、次の通りです。
- Rustで使いたいインタフェースやデータ型を定義している全てのCヘッダを集める
- ステップ1で集めたヘッダファイルを
#include "..."
するbinding.h
ファイルを書く bindgen
にbinding.h
を与えて実行するbindgen
の出力を、bindings.rs
にリダイレクトするbindings.rs
をinclude!
するlib.rs
を用意し、クレートとして利用できるようにする- ステップ5で作成したクレートをRustらしく使えるAPIに変換するラッパクレートを作成する
ステップ5で作成する自動生成されたコードのクレートは、-sys
という名前にすることが慣例になっています。
ベアメタル環境でバインディングを作る場合、標準ライブラリが使える環境と異なる点は、次の3点です。
bindgen
利用時に、コマンドラインなら--ctypes-prefix
にcty
オプションを使う、または、ビルドスクリプトならBuilder.ctypes_prefix("cty")
を使う- bindingクレートに
cty
クレートとの依存関係を追加する - bindingクレートに
#![no_std]
アトリビュートを追加する
#[no_std]
環境をターゲットにbindgen
をコマンドラインから利用する場合、例えば次のようなオプションを指定します。
bindgen --use-core --ctypes-prefix cty ...
--use-core
は、標準ライブラリの型を使わずにcore
クレートの型を使うコードを生成します。--ctypes-prefix cty
は、Cの型プレフィックスをcty
クレートの物が使われるようにします。
bindgen
で生成したRustコードを、クレートにまとめます。その際、cty
クレートの依存をCargo.toml
に追加します。
[dependencies]
cty = "0.2.0"
ほぼ定型作業ですが、bindgenで自動生成したコードはbindings.rs
としておき、lib.rs
からインクルードします。lib.rs
には、次の内容を書いておきます。
#![allow(unused)] #![no_std] #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] fn main() { include!("bindings.rs"); }
Cのソースコードは、Rust推奨のコーディングスタイルに沿っていないため、コンパイル時に警告が出ないようにlintルールを緩和します。加えて、#[no_std]
アトリビュートを追加します。
出典
- The Embedded Rust Book: 9.1.Rustと少しのC