S-JIS[2024-09-24/2024-10-06] 変更履歴

Rust cbindgenメモ

Rustのcbindgenツールのメモ。

  • Javaのjextract(C言語のヘッダーファイルからJava用のクラスを生成するツール)

概要

cbindgenは、C言語やC++のヘッダーファイルを生成するツール。

C言語で扱えるように定義した以下のような関数構造体等を収集して、C言語やC++のヘッダーファイルを生成する。
(Rustの関数を呼び出す言語向けにヘッダーファイルを提供する目的)

#[no_mangle]
pub extern "C" fn add(left: i32, right: i32) -> i32 {
    left + right
}
#[repr(C)]
pub struct MyStruct1 {
    value1: i32,
    value2: i32,
}

なお、implブロックでもextern "C"のメソッドを定義することは出来るが、C言語は構造体の中に関数を持てないので、普通にグローバルな関数として扱われてしまう。(関数名に構造体名が付加されるようなことも無い)


cbindgenのインストール

cbindgenはCargoを使ってインストールできる。

$ cargo install cbindgen

ユーザーのホームディレクトリー/.cargo/bin」の下にcbindgenというコマンドがインストールされる。
(Cargoをインストールしていれば、このディレクトリーへのパスは通っている)


ヘッダーファイルの生成

cbindgenを実行すればC言語のヘッダーファイルが生成できる。
実行する場所はプロジェクトディレクトリー直下。(cargo buildcargo runを実行するのと同じ場所)

--outputで、出力するヘッダーファイルのファイル名(パス)を指定する。
(--outputを付けないと、コンソールに出力される)

--typeで対象言語の種類を指定する。CはC言語、C++はC++言語。省略時はC++。

$ cbindgen --lang C --output ffi-example.h
$ cbindgen --lang C++ --output ffi-example.hpp

生成されたヘッダーファイルの例(C言語用)

ffi-example.h

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct MyStruct1 {
  int32_t value1;
  int32_t value2;
} MyStruct1;

int32_t add(int32_t left, int32_t right);

定数の例

定数(const)がヘッダーファイルにどのように出力されるか。[2024-09-25]

src/lib.rs:

pub const MY_CONST1: i32 = 123;

C++ヘッダーファイルの出力例

$ cbindgen

↓実行結果

〜
constexpr static const int32_t MY_CONST1 = 123;
〜

Cヘッダーファイルの出力例

$ cbindgen --lang C

↓実行結果

〜
#define MY_CONST1 123
〜

列挙型の例

cbindgenは(単純な)列挙型(enum)に対応している。[2024-09-25]

ただし、どこからも使われていない(externの関数で使われていない)列挙型は、デフォルトでは出力されない。[/2024-10-06]

src/lib.rs:

#[repr(C)]
pub enum MyEnum1 {
    Default,
    Interrupt,
    Wait,
    InterruptExclude,
    WaitExclude,
}

-v(verbose)を付けて見ると、列挙型自体は認識しているが、出力はされない。

$ cbindgen --version
cbindgen 0.27.0

$ cbindgen -v 
〜
INFO: Take ffi-example-rust::MyEnum1.
〜

cbindgen.tomlに列挙型名を明示してやったら出力される。

プロジェクト/cbindgen.toml:

[export]
include = ["MyEnum1"]
$ cbindgen --lang C

↓実行結果

typedef enum MyEnum1 {
  Default,
  Interrupt,
  Wait,
  InterruptExclude,
  WaitExclude,
} MyEnum1;

列挙子名の変更

デフォルトでは、ヘッダーファイルに出力される列挙子名は、Rustの列挙子のまま。つまりRustの命名ルールに従っていればCamelCaseである。
C言語的にはSNAKE_CASEにしたいかもしれない。

cbindgen.tomlに設定を入れてやると、列挙子名の変換が行われる。

プロジェクト/cbindgen.toml:

[enum]
rename_variants = "None"
rename_variants 出力例 説明
None
typedef enum MyEnum1 {
  Default,
  Interrupt,
  Wait,
  InterruptExclude,
  WaitExclude,
} MyEnum1;
無変換。
rename_variantsが指定されていない場合のデフォルト。
CamelCase
typedef enum MyEnum1 {
  default,
  interrupt,
  wait,
  interruptExclude,
  waitExclude,
} MyEnum1;
先頭が小文字のcamelCase。

この例だと「default」という列挙子名になっているが、このままC言語で使うとコンパイルエラーになりそう^^;
SnakeCase
typedef enum MyEnum1 {
  default,
  interrupt,
  wait,
  interrupt_exclude,
  wait_exclude,
} MyEnum1;
小文字のsnake_case。
ScreamingSnakeCase
typedef enum MyEnum1 {
  DEFAULT,
  INTERRUPT,
  WAIT,
  INTERRUPT_EXCLUDE,
  WAIT_EXCLUDE,
} MyEnum1;
大文字のSNAKE_CASE。
QualifiedScreamingSnakeCase
typedef enum MyEnum1 {
  MY_ENUM1_DEFAULT,
  MY_ENUM1_INTERRUPT,
  MY_ENUM1_WAIT,
  MY_ENUM1_INTERRUPT_EXCLUDE,
  MY_ENUM1_WAIT_EXCLUDE,
} MyEnum1;
接頭辞として列挙型名が付いたSNAKE_CASE。

C言語では、enumで定義した列挙子はそのままグローバル変数(定数)のように使える。
したがって、異なる列挙型であっても同名の列挙子は区別できない。
そこで、列挙型名が接頭辞として付くと便利。
LowerCase
typedef enum MyEnum1 {
  default,
  interrupt,
  wait,
  interruptexclude,
  waitexclude,
} MyEnum1;
全て小文字。
UpperCase
typedef enum MyEnum1 {
  DEFAULT,
  INTERRUPT,
  WAIT,
  INTERRUPTEXCLUDE,
  WAITEXCLUDE,
} MyEnum1;
全て大文字。

Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま