RustでFFI関連の練習をした。
すべてWindows MSVC環境。
FFI = Foreign Function Interface(他言語の関数を使うためのインターフェイス)。
異なる言語を組み合わせて使うための技術的仕様。
簡単に言えば「C言語の仕様に合わせる」ことで実現している。
※7/26追記
コメントでの指摘を受けて、誤っていた箇所を訂正。
※8/5追記
Pythonスクリプトの不具合を修正。
要点
Rust製ライブラリをC言語で使う
Rust製ライブラリをPythonで使う
C言語製ライブラリをRustで使う
補足
参考
要点
- C言語をコンパイルするには「x64 Native Tools Command Prompt」を使う。
-
Rust~C言語間では静的リンク(.lib)で連携させる。※7/26訂正 Rust~Python間では動的リンク(.dll)で連携させる。- リンク方法は大別して「動的(Dynamic)リンク」「静的(Static)リンク」がある。
- 本記事では言語ごとに異なるリンク方法を用いている。
- C, Python~Rustライブラリは動的リンク。
- Rust~Cライブラリは静的リンク。
Rust製ライブラリをC言語で使う
まずはRustで簡単なライブラリを作ってみる。
#[no_mangle]
pub extern "C" fn rs_add(a: isize, b: isize) -> isize {
a + b
}
これをライブラリとしてコンパイルする。
> rustc --crate-type="dylib" rs_add.rs
コンパイルすると下記4つのファイルが出力される。
-
rs_add.dll
←動的(Dynamic)リンクライブラリ。 -
rs_add.dll.exp
←.dllファイルを生成するための一時ファイル。不使用。 -
rs_add.dll.lib
←静的(Static)リンクライブラリ。C言語のコンパイル時にこのファイルが必要。 ※7/26訂正 -
rs_add.pdb
←Program Data Baseファイル。デバッグ情報等。不使用。
そしてC言語でプログラム本体を作る。
C言語製ライブラリから関数を呼び出す書き方とまったく同じである。
RustがC言語の仕様に合わせていることがわかる。
#include<stdio.h>
// Rustで作った関数の定義を記述
int rs_add(int a, int b);
int main() {
// Rust製関数を使用
int ans = rs_add(4, 6);
printf("%d", ans);
return 0;
}
C言語のコンパイルには「x64 Native Tools Command Prompt for VS 20xx」を使う。
Visual Studioを使うことも可能だが、この程度の小実験に持ち出すのは面倒くさい。
それと今回はFFIの仕組みを理解することが目的なので、Rustをcargo
を使わずrustc
でコンパイルしたのと同じように、不必要な処理を介在させないもっとも単純な方法でコンパイルしたい。
MSVC環境におけるC言語のコンパイラはcl
というコマンドだが、通常のコマンドプロンプトやPowerShellからでは使用できない。
Pathを設定すれば不可能ではないだろうが、他にもいくつかの環境変数を設定する必要がありわかりにくい。
「x64 Native Tools Command Prompt for VS 20xx」はそれらの環境変数が設定済みのコマンドプロンプトである。
RustのMSVC環境が正しく構築されていれば、スタートメニューに登録されているはず。
「x64用」「x86用」「クロスコンパイル用」とあるので、アーキテクチャに合った物を使おう。
そしてcl
コマンドでコンパイル。
> cd (作業ディレクトリ)
> cl use_rs.c rs_add.dll.lib
完成した実行ファイルuse_rs.exe
は「Native Tools~」からでも、通常のコマンドプロンプトやPowerShellからでも実行可能。
※7/26追記
ただしrs_add.dll
が同一ディレクトリに存在する必要がある。
> ./use_rs.exe
10
Rust製ライブラリをPythonで使う
Rust製ライブラリrs_add.dll
の作成までは前項と同じ。
Pythonでプログラム本体を作る。
import os
from ctypes import cdll
libfile = "rs_add.dll"
cwd = os.getcwd()
libpath = os.path.join(cwd, libfile)
rslib = cdll.LoadLibrary(libpath)
print(rslib.rs_add(4, 6))
これをrs_add.dll
と同一ディレクトリに配置して実行すればよい。
(os.getcwd()
を使っている関係で、ファイルのあるディレクトリで実行する必要あり)
> python ./use_rs.py
10
8/5修正以前の内容
import os
from ctypes import cdll
libfile = "rs_add.dll"
# 8/5修正箇所
libpath = os.path.join(os.path.dirname(__file__), libfile)
rslib = cdll.LoadLibrary(libpath)
print(rslib.rs_add(4, 6))
なおVS Codeのターミナル(PowerShell)、外部のコマンドプロンプトでは何故かうまく実行できなかった。
外部のPowerShellを使うか、VS Codeで「右クリック → ターミナルでPythonファイルを実行する」ならば実行成功。
※7/26追記
再試行したところ、外部のPowerShellでも実行できたりできなかったりした。
Pythonのカレントディレクトリが問題に関わっていそうな気がする。
課題。
なおuse_rs.py
をフルパスで指定すればどの環境からでも成功した。
> python C:/Users/(略)/rust/ffi/use_rs.py
10
修正の詳細はこちら。
C言語製ライブラリをRustで使う
RustとC言語の関係を逆にする。
#include<stdio.h>
int c_add(int a, int b) {
return a + b;
}
「x64 Native Tools Command Prompt for VS 20xx」でコンパイルして.libファイルを作る。
まずは.libファイルの素となる.objファイルを出力。
> cl /c c_add.c
c_add.obj
または以下のコマンドでも可。
この場合は2ファイルが出力される。
> cl c_add.c /LD
-
c_add.dll
← 不使用。 c_add.obj
そして.objファイルから.libファイルを作る。
これで準備完了。
> lib c_add.obj
-
c_add.lib
←静的(Static)リンクライブラリ。
.libファイルを使うRust製プログラム本体を作る。
use std::os::raw::c_int;
// static(静的)リンクを指定
#[link(name = "c_add", kind = "static")]
extern "C" {
fn c_add(a: c_int, b: c_int) -> c_int;
}
fn main() {
unsafe {
println!("{}", c_add(4, 6));
}
}
コンパイルして実行。
> rustc use_c.rs -L c_add.lib`
> ./use_c.exe
10
※7/26追記
これは前2項とは異なりライブラリが静的リンクされているので、c_add.lib
等のファイルが同一ディレクトリに置かれている必要がない。
つまりuse_c.exe
のみを別ディレクトリに移動させても実行できる。
補足
「x64 Native Tools Command Prompt」でcl /help
(またはcl /?
)とすればclの説明、コマンド一覧が表示できる(長い)。
> cl /help
(略)
-リンク-
/LD .DLL を作成する /LDd .DLL デバッグ ライブラリを作成する
/LN .netmodule を作成する /F<num> スタック サイズを設定する
/link [リンカー オプションとライブラリ] /MD MSVCRT.LIB でリンクする
(略)
参考