コンテンツにスキップ

RustでWMX3を使う

WMX3のAPIをRustで使いたい場合、v3.6u3時点ではRust APIとして提供はされていないため、そのままでは使えません。 しかし、FFIという仕組みを使ってC/C++のAPIをRustから呼び出すことができます。

FFIのやり方はいくつかありますが、こちらではbindgenというクレートを使った方法を紹介します。 また、WMX3 APIを一つ一つRust API対応するのではなく、C++でWMX3 APIを使った処理をいくつかの関数にまとめ、それをCの関数としてRustから利用する方式とします。

コードは以下にあります。

bindgenを使用しています。以下の準備が必要です。

実行するには、wmx3-rust-fficargo runします。 すると、デバイス生成し、10秒間1秒毎に軸0の指令位置を出力し、デバイスを破棄します。

コード説明

ファイル

  • ディレクトリsrc
    • ディレクトリffi
      • wmx.cpp
      • wmx.h
    • lib.rs
    • main.rs
  • .gitignore
  • Cargo.lock
  • Cargo.toml
  • build.rs

C側処理

./src/ffi/にWMX3 APIの処理をまとめたC言語の関数を実装します。 今回は、以下の3つです。

./src/ffi/wmx.cpp
int open_wmx();
int close_wmx();
double get_pos(int axis);

実装は./src/ffi/wmx.cppです。 C言語の関数として実装するので、wmx.hextern "C"で囲います。

open_wmx()close_wmx()でデバイスの生成と破棄、get_pos(int axis)で軸axisの指令位置を取得します。 詳細はGitHubで確認してください。

Cプログラムのコンパイル

Cのプログラムをcargo buildで同時にコンパイルできるようにします。 build.rsは、cargo buildで最初に実行されるプログラムです。ccクレートでコンパイルします。

また、WMX3 APIを使う場合、IMDll.dllをexeと同じディレクトリに置く必要があるため、これを自動化します。

./build.rs
se std::env;
use std::fs;
use std::path::PathBuf;
fn main() {
// WMX3 APIライブラリのパスを指定
println!("cargo:rustc-link-search=C:\\Program Files\\SoftServo\\WMX3\\Lib\\");
// 使用するWMX3 APIライブラリを指定
println!("cargo:rustc-link-lib=WMX3Api");
println!("cargo:rustc-link-lib=CoreMotionApi");
println!("cargo:rustc-link-lib=IMDll");
// WMX3 APIを使う場合、VS2015以降のコンパイラでは以下のライブラリが必要
println!("cargo:rustc-link-lib=legacy_stdio_definitions");
println!("cargo:rustc-link-lib=legacy_stdio_wide_specifiers");
// .h, .cppの変更を検出させる
println!("cargo:rerun-if-changed=./src/ffi/wmx.cpp");
println!("cargo:rerun-if-changed=./src/ffi/wmx.h");
// C++コンパイル
cc::Build::new()
.file("./src/ffi/wmx.cpp")
.include("./src/ffi/wmx.h")
.compile("wmxffi");
// ~省略~
// IMDll.dllを.exeと同じディレクトリにコピーする
let src = "C:\\Program Files\\SoftServo\\WMX3\\Lib\\IMDll.dll";
let dest = out_path.join("IMDll.dll");
fs::copy(src, &dest).unwrap();
}

CプログラムのFFIコード生成

bindgenでCプログラムのFFIコードを自動生成します。

まず、build.rsbindgenによる自動生成を行います。

./build.rs
se std::env;
use std::fs;
use std::path::PathBuf;
fn main() {
// ~省略~
// bindgenでFFI自動化
let bindings = bindgen::Builder::default()
.header("./src/ffi/wmx.h")
.clang_arg("-Iffi")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
// $OUT_DIR/bindings.rs にFFIコードを書き込み
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
// ~省略~
}

これにより、OUT_DIRbindings.rsというファイルが生成されます。

./src/lib.rsbindings.rsをインクルードし、ライブラリとしてビルドします。

./src/lib.rs
// FFIコードをコンパイル
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

#![allow...というのは、FFIする関数や型はrustのコーディング規約に則っていないことがおおいため、警告を出さないようにするためのものです。

これでRust側からunsafeな関数としてCの関数を使う準備ができました。

Rust側の実装

main.rsでCのAPIを使ってみます。

./src/main.rs
use std::{thread::sleep, time::Duration};
// lib.rsでビルドしたFFIコードを参照
use wmx3_rust_ffi::*;
fn main() {
let ret = unsafe { open_wmx() };
println!("open_wmx = {}", ret);
for _ in 0..10 {
let pos = unsafe { get_pos(0) };
println!("Axis0 Pos = {}", pos);
sleep(Duration::from_secs(1));
}
let ret = unsafe { close_wmx() };
println!("close_wmx = {}", ret);
}

bindgenで生成したRust APIはunsafeなAPIですので、unsafe {}で囲ってやる必要があります。