C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Node.js と FFI の 2020-03-18 時点でのメモ; node-ffi 系 → node-ffi-napi 系

今回のメモの Node.js は:

node -v
v13.5.0

です。先ずは Node.js 初心者らしく npm i ffi して死にました:

npm i ffi

f:id:USAGI-WRP:20200318171359p:plain

ほか多数のエラーを観測し、死んでしまいました。☠

f:id:USAGI-WRP:20200318170856p:plain

どんどん使われなくなっているので何か代替に置き換わったのだろうと思い調べてみると、 ffi は Node.js < 11 の時代で死んでしまったらしい事、 Node >= 11 用には fork した @saleae/ffi が対応しているらしい事がわかりました。

既にこちらも Weekly download が怪しいですが、とりあえず npm i @saleae/ffi してみました:

npm i @saleae/ffi

f:id:USAGI-WRP:20200318171506p:plain

死亡。☠

ここで元々の ffi の Issues を眺めてみると、

が見つかり、

  1. ffi-napi が Node >= 12 用に使えるらしい
  2. ffi-napi はコールバックの実装にまだ問題があるかもしれないらしい(このメモより14日前の情報)

事がわかりました。コールバックの問題点は、

  1. Fix crasher in callback calls by atishay · Pull Request #56 · node-ffi-napi/node-ffi-napi · GitHub (このメモより6日前に merged )
  2. Port fix for Multithreaded callback crash by atishay · Pull Request #50 · node-ffi-napi/node-ffi-napi · GitHub (このメモより2ヶ月くらい前に merged )

あたりについて、特に日付的に #56 の方の注意だと思います。現在は解決しているようです。とりあえず ffi-napi もインストールチャレンジしてみました:

npm i ffi-napi

しばらくのち:

+ ffi-napi@2.4.7
added 10 packages from 57 contributors, removed 4 packages and audited 17 packages in 18.686s
found 0 vulnerabilities

エラーなく導入できました。👍

実際に使う場合は型のバインディングref も欲しいのですが、こちらも node-ffi-napi シリーズの repos が一通りあるので:

npm i ref-napi します:

npm i ffi-napi

しばらくのち:

+ ref-napi@1.4.3
updated 1 package, moved 1 package and audited 23 packages in 5.993s
found 0 vulnerabilities

ref-napi も導入できました。👍

これでFFIの最低限の準備が整いました。適当に動作テストしてみます。

(1) テスト用に FFI で呼ばれる native な .dll (.so, .dylib) を作ります:

今回はなんとなく Rust で呼ぶと "にゃおーん" 文字列を返してくれる nyanko.dll を作ります。

# Windows でやっている場合は PowerShell で echo すると utf16 になるので 
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs

# UTF8 に一手間増えます(Windowsの謎仕様でBOMが付きますが rustc はスルーしてくれます)
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs

# このくらいなら Cargo さんに頑張ってもらわなくても rustc 直呼びで手早く済ませられます
rustc --crate-type cdylib nyanko.rs

これで Node.js から ffi-napi により FFI で呼ばれる側の nyanko.dll ができました。一応 nyanko.rs のソースだけ書き残しておくと

#[no_mangle] pub extern fn f() -> * const u8
{
  "にゃおーん".as_ptr()
}

(2) テスト用に FFI で呼ぶ側の Node.js な tester.js を作ります:

echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js

ワンライナーにしちゃうとこちらはしんどいですね…。素直に書いたソースは:

var ffi = require('ffi-napi')
var ref = require('ref-napi')
var s = ref.types.CString
var nyanko = ffi.Library( 'nyanko.dll',{ 'f': [ s, [] ] } )
console.log( nyanko.f() )

です😏

Node.js で tester.js を実行して node-ffi-napi / node-ref-napi が期待動作しネイティブライブラリーの nyanko.dll から "にゃおーん" が無事に返り表示されるはずです:

node tester.js
にゃおーん

成功しました👍

コンパクト化したコマンドライン

ついでに npm -> yarn 化。

# テスト用のディレクトリーと npm init していない場合はここから
mkdir myapp
cd myapp
yarn init -y
# ffi-napi, ref-napi を追加
yarn add ffi-napi ref-napi
# テスト用の native dll を作成
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs
rustc --crate-type cdylib nyanko.rs
# テスト用の .js を作成
echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js
# 動作確認
node tester.js
にゃおーん

参考