RustでMATLAB C APIを呼び出してMATファイルを作る


唐突だがMATLABのMATファイルをRustで作りたくなった。
pythonだとscipy.io.savematとscipy.io.loadmatで保存と読み込みができる。
Rustだとどうなのだろうと思って調べてみたところ、こんなクレートを発見した。
良さそうと思って使ってみたが、どうもまだ読み込みしかできないようだ。

自作するのもめんどくさいので、MATLAB C API を使えないかな〜と思って試行錯誤したメモ帳。

Cで書いた関数をRustで呼ぶ

Rust はCの関数をFFIで呼び出せる。

src/hello.c
#include <stdio.h>

int hello (void) {
    printf ("Hello World! [from C]\n");
    return 0;
}

Rustからは以下のようにして呼ぶ。

src/main.rs

extern {
    fn hello ();
}

fn main() {
    unsafe { hello () };
    println! ("Hello World! [from Rust]");
}

cargo でCのソースも一緒にビルドするために、Cargo.toml を以下のように書く。

Cargo.toml
[package]
name = "ffi_c"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2"

[build-dependencies]
cc = "1.0"

また、Cargo.tomlと同じディレクトリにbuild.rsというファイルを作る。

build.rs

extern crate cc;

fn main () {

    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .file ("src/hello.c")
        .compile ("libfoo.a");
}

これで cargo build すると libfoo.a が作られ、本体のプログラムがコンパイル&リンクされる。

Cで書いた引数・返り値ありの関数をRustで呼ぶ

引数や返り値を与えたい場合は以下のようにする。

src/double.c
double double_input (double input) {
    return 2 * input;
}
src/smain.rs
extern crate libc;

extern {
    fn hello ();
    fn double_input (input: libc::c_double) -> libc::c_double;
}

fn main() {
    unsafe { hello () };
    println! ("Hello World! [from Rust]");


    let input = 4.0;
    let output = unsafe { double_input (input) };
    println! ("{} * 2 = {}", input, output);
}
build.rs
extern crate cc;

fn main () {
    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .files (vec!["src/hello.c","src/double.c"])
        .compile ("libfoo.a");
}

libcクレート でCの型をが定義されているので、これを用いる。

複数のCファイルを用いる場合は、 build.rs のfileをfiles に変えて引数をイテレータにすればいいらしい。

MATファイルを作る

MATLAB C API のサンプルである matlabroot/extern/examples/eng_mat/matcreat.c を使ってMATファイルを作らせる。
matcreat.c をsrcフォルダにコピーして、main関数をmatcreat関数に名前を変えておく。

src/main.rs

extern crate libc;

extern {
    fn matcreat () -> libc::c_int;
}

fn main() {
    unsafe { matcreat () };
}

matcreat.cはMATLABのライブラリを呼び出さないといけないので、それをbuild.rsに追記する。

build.rs
extern crate cc;

fn main () {
    println!("cargo:rustc-link-search=native=matlabroot/bin/maci64");
    println!("cargo:rustc-link-lib=mx");
    println!("cargo:rustc-link-lib=mat");

    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .file ("src/matcreat.c")
//        .object ("matlabroot/bin/maci64/libmx.dylib")
//        .object ("matlabroot/bin/maci64/libmat.dylib")
        .include ("matlabroot/extern/include")
        .compile ("libfoo.a");
}

私はMacを用いているのでmaci64ディレクトリに色々入っているが、LinuxやWindowsだと別の名前になる。

途中でコメントアウトされているobject() は静的ライブラリのパスを引数にとるらしく、動的ライブラリのパスを渡したらwarningを吐かれた。

これで無事matファイルがされる。

終わりに

できればRustから直でMATファイルを読み書きできるやつが欲しい。

参考サイト

https://scrapbox.io/yuta0801/Rust%E3%81%A7C%2FC++%E3%81%AE%E9%96%A2%E6%95%B0%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B
http://mmi.hatenablog.com/entry/2017/02/28/213656
https://qiita.com/kjunichi/items/31aef0cf3f4f7fe6dc32
https://teratail.com/questions/157950