cbindgen を使って C/C++ から Rust を呼び出す
はじめに
cbindgen は、C/C++ のプログラムから Rust を呼び出すときに使えるバインディングを生成してくれるクレートである。
本稿では、実際にプロジェクトを作成して Rust や C/C++ を書く流れに沿って、cbindgen の基本的な使用方法を紹介する。
なお、以下の説明では Rust や C/C++ のビルドに必要なツール群が既にインストールされていることを前提としている。また、OS は Windows か Linux を想定しているが、macOS の場合でも本稿の解説と同様な手順で動作させることができる。
Rust でライブラリを書く
まず、cargo
を使用して新しい Rust のプロジェクトを作成する。
以下のように、コマンドを実行する。 sample-project
の部分がプロジェクト名になるので、好きな名前をつけてほしい。
cargo new --lib sample-project
今作ったプロジェクトのディレクトリに移動し、cbindgen を依存として追加する。
--build
オプションを指定することで、ビルド時に使用する依存として cbindgen を追加することができる。
cd sample-project
cargo add --build cbindgen@0.24.3
次に、C/C++ から呼べるようにライブラリをコンパイルする設定を加える。
Cargo.toml
を開き、以下の項目を追加する。
crate-type
として cdylib
を指定することで、C/C++ から呼び出せる動的ライブラリがコンパイルされる。
最後に、src/lib.rs
を、以下のように書く。
この関数は、引数 arg
として8ビット長の正の整数を受け取り、その内容を標準出力に表示する。さらに、arg
に10を加えた数を返す。
なお、関数には、no_mangle
¹ と extern "C"
² を付ける必要がある。
ビルドスクリプトを書く
プロジェクトのルートフォルダに build.rs
というファイルを作成し、ビルドスクリプトを書く。このファイルに書かれた内容は、ビルド時に自動で実行される。
build.rs
を以下のように書くと、Rust のコードをビルドする度に cbindgen のプログラムが呼び出され、rust-lib.h
という名前でヘッダファイルを生成してくれるようになる。なお、出力ファイル名の指定は自由に変更できる。
また、以下のように書くと、ヘッダファイル生成に関するオプションを追加することができる。
cbindgen のオプションはいくつかある。そのうちの一部を紹介する。
pragma_once
:インクルードガード#pragma once
を追加するlanguage
:出力するヘッダファイルの対象をC
,C++
,Cython
から選べる。cpp_compat
:ヘッダファイルのextern "C"
を#ifdef __cplusplus
などで包んで C++ との互換性をもたせる。上述のlanguage
がC
のときのみ有効。
詳しくは、以下の cbindgen のドキュメントを参照してほしい。
ライブラリとヘッダファイルを生成する
以下のコマンドを実行すると、ライブラリとヘッダファイルが生成される。
cargo build
Windows の場合、target/debug/
以下に sample_project.dll
と sample_project.dll.lib
が出力される。Visual Studio を利用した場合と異なり、sample_project.lib
という名前ではないので注意が必要。
Linux の場合は、同じディレクトリに libsample_project.so
が出力される。
また、ヘッダファイルは rust-lib.h
として出力される。
C/C++ のコードを書く
関数の呼び出し側となる C/C++ のコードを書く。今回は C++ を使用する。
自動生成されたヘッダファイルをインクルードして、関数を呼び出す処理は、以下のように記述できる。
C/C++ のコードをビルドする
今書いたコードをビルドする。ヘッダファイルとライブラリを指定して、コンパイラに読み込んでもらう必要がある。以下の説明では、先ほど書いた C++ のコードの名前を caller.cpp
と仮定する。
Windows では、Visual Studio に付属する x64 Native Tools Command Prompt を開き、以下のコマンドを実行してビルドする。結果として実行可能ファイル caller.exe
が生成される。
cl.exe caller.cpp target\debug\sample_project.dll.lib
Linux の場合は以下のようにビルドでき、実行可能ファイル a.out
が生成される。
g++ caller.cpp -Ltarget/debug -lsample_project
実行する
バイナリが出力されたので、実行してみる。
Windows の場合は以下のように実行できる。ただし、実行前に sample_project.dll
を caller.exe
と同じディレクトリにコピーする必要がある。
copy target\debug\sample_project.dll .
caller.exe
Linux の場合は以下のように実行できる。libsample_project.so
を読み込むために、環境変数 LD_LIBRARY_PATH
を指定している。
LD_LIBRARY_PATH=./target/debug ./a.out
どちらの場合も実行結果は以下のようになり、正しく Rust の関数を呼び出せていることがわかる。
arg: 30
ret: 40
いろいろな型の変換
cbindgen によって生成されるヘッダファイルには、Rust で書いたいろいろな要素が C/C++ の形式で出力される。
変換元の Rust コードと、出力されたヘッダファイルの例を以下に示す。
文字列の受け渡しは以下のように行う。Rust 側では、c_char
型へのポインタとして文字列を受け取り、関数内で &str
型に変換して利用する。
ヘッダファイルの生成の際は、引数の型が char
へのポインタへと自動的に変換される。
おわりに
本稿で紹介したように、cbindgen を使用すると Rust の関数を呼び出すのが大変便利になる。
Rust から C/C++ のコードを呼ぶ機会に比べると、C/C++ から Rust のコードを呼ぶパターンは少ないかもしれないが、今まで C/C++ で作っていたライブラリ部分を Rust で書き直す、といったような場合には有用であるため、ぜひ使ってみていただきたい。
[1] Rust の既定では、コンパイラによって関数の名前が自動的に改変される仕様となっている。この操作をマングリング(Mangling)と呼ぶ。
no_mangle
指定をするとマングリングが無効化され、書いたままの関数名でライブラリに収めることができる。したがって、関数を Rust の外から呼び出したいときは、マングリングしないように指定する必要がある。
Rust のマングリング・アルゴリズムの詳細については、以下のドキュメントにまとめられている。
[2] C 言語の ABI(Application Binary Interface)を使用するように指定する。これを指定することによって、C/C++ から Rust の関数を呼べるようになる。
ちなみに、extern には、"C"
以外にも "cdecl"
や "stdcall"
など、さまざまな種類がある。これらの詳細については、以下のドキュメントにまとめられている。