RustでWMX3を使う
This content is not available in your language yet.
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-ffiでcargo runします。
すると、デバイス生成し、10秒間1秒毎に軸0の指令位置を出力し、デバイスを破棄します。
コード説明
ファイル
Directorysrc
Directoryffi
- wmx.cpp
- wmx.h
- lib.rs
- main.rs
- .gitignore
- Cargo.lock
- Cargo.toml
- build.rs
C側処理
./src/ffi/にWMX3 APIの処理をまとめたC言語の関数を実装します。
今回は、以下の3つです。
int open_wmx();int close_wmx();double get_pos(int axis);実装は./src/ffi/wmx.cppです。
C言語の関数として実装するので、wmx.hはextern "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と同じディレクトリに置く必要があるため、これを自動化します。
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.rsでbindgenによる自動生成を行います。
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_DIRにbindings.rsというファイルが生成されます。
./src/lib.rsでbindings.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を使ってみます。
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 {}で囲ってやる必要があります。