6-1. RustからCを呼ぶ

ここでは、RustのソースコードからC言語のソースコードを呼び出す方法を説明します。やることは2つです。

  1. CのAPIを、Rustで使えるようにインタフェースを定義する
  2. 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で紹介します。

標準ライブラリが使える環境と、ベアメタル環境とで共通する手順は、次の通りです。

  1. Rustで使いたいインタフェースやデータ型を定義している全てのCヘッダを集める
  2. ステップ1で集めたヘッダファイルを#include "..."するbinding.hファイルを書く
  3. bindgenbinding.hを与えて実行する
  4. bindgenの出力を、bindings.rsにリダイレクトする
  5. bindings.rsinclude!するlib.rsを用意し、クレートとして利用できるようにする
  6. ステップ5で作成したクレートをRustらしく使えるAPIに変換するラッパクレートを作成する

ステップ5で作成する自動生成されたコードのクレートは、-sysという名前にすることが慣例になっています。

ベアメタル環境でバインディングを作る場合、標準ライブラリが使える環境と異なる点は、次の3点です。

  1. bindgen利用時に、コマンドラインなら--ctypes-prefixctyオプションを使う、または、ビルドスクリプトならBuilder.ctypes_prefix("cty")を使う
  2. bindingクレートにctyクレートとの依存関係を追加する
  3. 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]アトリビュートを追加します。

出典