siar

Snowy Institute

Rustからhidapiを叩くときのリンクエラーについて

Rustから hidapi というクレートを介して,Cライブラリ hidapi を利用するときの,リンクに関するお話です.

ご無沙汰しております.時間と心の余裕ができたので,ブログの方もぼちぼち更新していきたいと思います.

HID

計算機の周辺機器の分類として,HID (human interface device) というものがあります.HIDにはキーボード,マウス,ゲームパッドなど,入出力を司る様々なデバイスが含まれます.

これらのドライバは,主にカーネルに近い部分で,C言語によって記述されることが一般的です. hidapiは,Linuxを使っていれば一度は見たことがあるであろう libusb を基盤とする,HID用ライブラリです. 最もよく使われているHID用ライブラリとも言えるでしょう.

github.com

hidapi-rs

ところで,半年前くらいに hidapi という,Rust向けにhidapiをラップしたクレートを見つけました.

github.com

折角なので,触ってみました.環境はUbuntu 18.04,Rust 1.59.0 (stable, 9d1b2106e 2022-02-23)です.

お試しとエラー

サンプルに従って,次のようなコードを書き,cargo runで実行します.

extern crate hidapi;
use hidapi::{DeviceInfo, HidApi, HidError};

fn main() {
    let api = HidApi::new().expect("Failed to create API instance");
    let device = api
        .open(vendor_id, product_id)
        .expect("Failed to open the device");
}

Cargo.tomlには以下のような依存を記述します.

[dependencies]
hidapi = {version = "1.3.3"}

すると,ビルドは通過したものの,以下のようなエラーが発生しました.

thread 'main' panicked at 'Failed to open the device: HidApiError { message: "hid_error is not implemented yet" }'

このhid_error is not implemented yetというエラーは,元のCライブラリ側で定義されたものです: https://github.com/libusb/hidapi/blob/2551a5d84baca323ca8d61db06464cd1b963c289/libusb/hid.c#L1470-L1474

ローカルでビルドする

直感的にリンクエラーの予感がしたため,hidapi-rsを直接クローンし,ローカルでビルドしてみました. すると,ビルドに失敗しました.

warning: cc: error: etc/hidapi/libusb/hid.c: No such file or directory
warning: cc: fatal error: no input files
warning: compilation terminated.

error: failed to run custom build command for `hidapi v1.3.3 (/home/yuki/lib/hidapi-rs)`
(中略)

  running: "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-m64" "-I" "etc/hidapi/hidapi" "-I" "/usr/include/libusb-1.0" "-Wall" "-Wextra" "-o" "/home/yuki/lib/hidapi-rs/target/debug/build/hidapi-4ba8afab133805e1/out/etc/hidapi/libusb/hid.o" "-c" "etc/hidapi/libusb/hid.c"
  cargo:warning=cc: error: etc/hidapi/libusb/hid.c: No such file or directory
  cargo:warning=cc: fatal error: no input files
  cargo:warning=compilation terminated.
  exit status: 1

  --- stderr

  error occurred: Command "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-m64" "-I" "etc/hidapi/hidapi" "-I" "/usr/include/libusb-1.0" "-Wall" "-Wextra" "-o" "/home/yuki/lib/hidapi-rs/target/debug/build/hidapi-4ba8afab133805e1/out/etc/hidapi/libusb/hid.o" "-c" "etc/hidapi/libusb/hid.c" with args "cc" did not execute successfully (status code exit status: 1).

etc/hidapi/libusb/hid.cというファイルがないと言っています.そもそもそんなものを入れた記憶はありません. そこで,hidapi-rsのCargo.tomlを見てみると,リンクするライブラリを指定するオプションがあることに気付きました: https://github.com/ruabmbua/hidapi-rs/blob/5227f8374debaf65efad8a0bf27a1058c08bcd03/Cargo.toml#L19-L26

build.rsを参照し,適切であろう linux-shared-hidraw を選択すると,hidapi-rsのビルドに成功しました.

(ちなみにaptでlibhidapi-devを入れておく必要がありますが,この辺の依存関係の解消方法はREADMEに書くか,ビルドスクリプトを用意する以外に思いつきません.誰かいい方法あれば教えてください)

解決方法

さて,自分で書いたCargo.tomlの依存部分を以下のように書き換えます.

[dependencies]
# before: hidapi = {version = "1.3.3"}
hidapi = {version = "1.3.3", default-features = false, features = ["linux-shared-hidraw"]}

features = ["linux-shared-hidraw"]は自然だと思いますので,説明を省略します. default-features = falseを入れないと,元のデフォルトオプションが全て有効化されてしまうことに注意が必要です. 今回の場合だと,default = ["linux-static-libusb", "illumos-static-libusb"]となっているので,これにlinux-shared-hidrawのオプションが追加され,リンクするライブラリが競合してしまいます.

この書き換えにより,最初に載せた簡単なプログラムが実行できるようになりました.

まとめ

Cargo経由での依存パッケージのビルドでは,なぜかCライブラリのリンクエラーが検出されず,コンパイル成功の判定が出てしまうことが分かりました. その結果,実行時にリンクエラー的なものが出るという現象を観測しました.

上述したように,依存パッケージをローカルでビルドしようとすると,きちんとエラーが出てくれます. そのため,今のところは(手間がかかりますが)この手順を踏むのが早いのではないかと思います.