LoginSignup
7
12

More than 5 years have passed since last update.

node-ffi 使い方メモ おまけExpressから実行時にハマったメモ

Last updated at Posted at 2017-11-06

共有ライブラリを使いたかった

調べてみるとnode-ffiというモジュールで実行できるとのこと。
しかしなかなかハマったのでメモ

仮定

仮にMylib.dllというC言語で書かれたdllがこのスクリプトと同じディレクトリにあるとします

Mylib.dllは以下の様な関数があります。
関数名→RepeatTxt
引数1→char*で文字列のメモリポインタ
引数2→int32で繰り返す回数を指定
返り値→char*でテキストを指定回数繰り返した文字列のメモリポインタが帰ってくる

この関数に引数を渡して、返ってきた文字列をコンソールログするみたいな想定で行きます。

まずは関数定義

ffiでライブラリを使う場合はまずライブラリにある関数をJS上で定義します。

  dll = "ライブラリのパス"
    mylib = ffi.Library(dll, {
        '関数名': ["返り値の型", ["引数1の型", "引数2の型"]],
    })

今回の場合は以下の様な感じ

  dll = "./MYlib"
    mylib = ffi.Library(dll, {
        'RepeatTxt': ["pointer", ["pointer", "int32"]],
    })

pointerはそのままメモリのポインタのことです。char*はpointerでもchar*でもOK

実行する


var output = mylib.RepeatTxt("テスト", 2)//これじゃ動かない

こんな感じで実行したくなりますが、これではだめです。
”テスト”はポインタじゃない・・・・

引数を作る

node-ffiは賢いモジュールなのでバッファー型のオブジェクトを渡してあげればそこからポインタを抜き出してライブラリに渡してくれます。
なので、char*の引数を作るには

var txt = "テスト"
var txt_buf = Buffer.from(txt, "utf8")

こんな感じにして文字列からバッファを生成します。

ちなみにint32は普通にNumber型を渡しても問題ないです。


var output = mylib.RepeatTxt(txt_buf, 2)

これでやっと実行することが出来ます。

返り値を取り出す。

上記のように実行した場合、outputにメモリのポインタが格納されます。
これを普通に出力すると、12 とか よくわからないものが返ってきます。
ポインタから文字列に変換するしくみが必要です

そこでrefというモジュールの登場です。
これはメモリポインタの取得や読み込みに関するモジュールで、
ffiで取得したポインタからいい感じに値を取り出したり出来る見たいです。
いろいろ使い道はあるようですが、私は以下の使い方しかわかりません、

refモジュールを使って値を取り出します。


var result = ref.readCString(output)

"readCString"関数はC言語はC++で記述されたChar*のメモリポインタを読み込んでJSの文字列に変換してくれるみたいですね。

くっつけるとこんな感じ


var ref = require("ref")
var ffi = require("ffi")

dll = "./MYlib"
mylib = ffi.Library(dll, {
    'RepeatTxt': ["pointer", ["pointer", "int32"]],
})

var txt = "テスト"
var txt_buf = Buffer.from(txt, "utf8")

var output = mylib.RepeatTxt(txt_buf, 2)

var result = ref.readCString(output)

console.log(result)

おまけ

おまけでさらにハマったのがExpressから実行するとライブラリが読み込め無かったこと。
コマンドライン実行では問題ないのですが、Expressから実行するとなぜかダメ
結果的に、child_processを使って実行するようにしたら問題なく動きました。

もう一つ、child_processで実行後、その返り値のポインタをExpress側に返した場合、どうもメモリが上書きされるみたいで、ref.CString()しても正常に読み込むことが出来ませんでした。
なので、子プロセス側でref.CString()してやって、文字列にしてからExpress側の戻す必要があるようです。

あと、関数の宣言時点でエラーを吐いてる場合は
・ライブラリのパスが間違っている(見えていないことも)
・ライブラリとnode.jsの実行環境bit数が違う
ことがよくあるみたいです。

追記

ffiから返却される値がメモリポインタだった場合、そのメモリポインタの指す先のメモリの中身がガベージコレクトの対象になる場合があるみたいです。

ref.CString(pointer)←あくまでnode.jsから直接参照するのは変数pointerであって、その先のメモリの中身はメモリ解放しても問題ないものとして扱われているのかな?と思います。

参考

https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial
https://tootallnate.github.io/ref/

7
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
12