[Rust] 簡単なFFI実験（Rust ～ C, Python）

RustでFFI関連の練習をした。
すべてWindows MSVC環境。

FFI = Foreign Function Interface（他言語の関数を使うためのインターフェイス）。
異なる言語を組み合わせて使うための技術的仕様。
簡単に言えば「C言語の仕様に合わせる」ことで実現している。

※7/26追記
コメントでの指摘を受けて、誤っていた箇所を訂正。
※8/5追記
Pythonスクリプトの不具合を修正。

[要点](#要点)
[Rust製ライブラリをC言語で使う](#rust製ライブラリをc言語で使う)
[Rust製ライブラリをPythonで使う](#rust製ライブラリをpythonで使う)
[C言語製ライブラリをRustで使う](#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で簡単なライブラリを作ってみる。

```rust:rs_add.rs
#[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言語の仕様に合わせていることがわかる。

```c:use_rs.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環境が正しく構築されていれば、スタートメニューに登録されているはず。
![pic.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565632/5cef660a-b705-ba1b-67d5-89ac3e9d2826.png)
「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でプログラム本体を作る。

```python:use_rs.py
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
```

<details><summary>8/5修正以前の内容</summary>

```python:use_rs.py
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
```
</details>

修正の詳細は[こちら](https://qiita.com/V_lasergun/items/b140077a9c35840ffe51)。

# C言語製ライブラリをRustで使う

RustとC言語の関係を逆にする。

```c:c_add.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製プログラム本体を作る。

```rust:use_c.rs
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 でリンクする
（略）
```

# 参考

https://www.socym.co.jp/book/1351

https://gihyo.jp/book/2019/978-4-297-10559-4

https://doc.rust-lang.org/std/os/raw/type.c_int.html

https://www.weblio.jp/content/Foreign+function+interface

http://gyabo.sakura.ne.jp/tips/vcdll.html

https://qiita.com/iwatake2222/items/3caf294726918c3af9e6

https://mmi.hatenablog.com/entry/2017/02/28/213656

https://csharp.keicode.com/devenv/what-is-pdb.php

https://www.sophia-it.com/extension/content/.exp