このエントリは Node.js Advent Calendar 2015の 11 日目のエントリです。
はじめに
1日目のエントリでコントリビューションの件がありました。Node.js本体への貢献はまだ出来ずにいますが、 今年は、
からのやり取りに始まり、node-ffiへ若干のコントリビュートができたました。 (まぁ、いろいろフォークされてるブランチを漁ってパッチを作っただけじゃねぇかと 言われればそれまでなんですがw)
そんなこともあり、node-ffiの紹介とその応用をテーマにエントリーを書きました。
node-ffiとは
Node.jsでC++を一切書かずに、共有ライブラリ(.so/.dll/.dylib)を呼び出すことが出来るモジュールです。
Node.jsは頻繁にネイティブモジュールのAPIが更新されるので ヘタレな、自分は
と音を上げてしまっています。でも、node-ffiを使えば、そんな悩みから開放されます。
ただし、node-ffiのREADMEにもありますが、呼び出しのオーバーヘッドが高いので、node.jsと使いたいライブラリ間を 頻繁に呼び出しあうような必要があるものは、パフォーマンスが出ないとのことです。
共有ライブラリ側にJsの関数を関数ポインタとして渡せるので、共有ライブラリ側から Js側の関数を呼び出すことも出来ます。以下のようなコードイメージとなります。
typedef int (*FUNCPTR)(char *script); int boot(char *name, FUNCPTR cb) { (*cb)("foobar"); return 0; }
// C側に渡す関数ポインタの定義 const funcPtr = ffi.Function('int', ['string']); const mylib = ffi.Library('libhoge', { // libhogeないでJS側から使う関数を定義 'boot': ['int', ['string', funcPtr]] }); // Cのライブラリ側から呼び出されるコールバック関数 const onResult = function(resultVal) { console.log('Result is', resultVal); return 0; }; // Cのライブラリの呼び出し mylib.boot("", onResult);
Goで作った共有ライブラリをNode.jsで動かす
C/C++以外でもRustやGoなどもCから呼び出せる共有ライブラリを 出力することができるので、こららの言語で共有ライブラリを出力すれば、これも同様にNode.jsから呼び出せます。
以下は、Node.js v5.1.1を使って動かしました。
準備
node-ffiを以下の様にインストールします。 node-ffiではないことに注意です。
npm install ffi
cgoで共有ライブラリを作る
以下が参考になりました。
ライブラリのコード
package main import ( "C" "fmt" "os" "github.com/shiena/ansicolor" ) //export colorprint func colorprint(cstr *C.char) { w := ansicolor.NewAnsiColorWriter(os.Stdout) text := "%sforeground %s "+C.GoString(cstr) + " %s %sbackground%s\n" fmt.Fprintf(w, text, "\x1b[31m", "\x1b[1m", "\x1b[21m", "\x1b[41;32m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[32m", "\x1b[1m", "\x1b[21m", "\x1b[42;31m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[33m", "\x1b[1m", "\x1b[21m", "\x1b[43;34m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[34m", "\x1b[1m", "\x1b[21m", "\x1b[44;33m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[35m", "\x1b[1m", "\x1b[21m", "\x1b[45;36m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[36m", "\x1b[1m", "\x1b[21m", "\x1b[46;35m", "\x1b[0m") fmt.Fprintf(w, text, "\x1b[37m", "\x1b[1m", "\x1b[21m", "\x1b[47;30m", "\x1b[0m") } func main() { }
Linux
go build -buildmode=c-shared -o libtest.so test.go
OSX
go build -buildmode=c-shared -o libtest.dylib test.go
Windows
go build -buildmode=c-shared -o libtest.dll test.go
と思いきや、Windwosでは32ビットも64ビットもともに未サポートでした。。 折角、windowsでも動くカラーのコンソール出力ライブラリを見つけたのに残念です。
なので、Windows 10 64ビットでRustならば、MSVC版つかってnode-ffiで動かせそうなので、 あとで記事を書く予定です。
作成したライブラリを動かす
node-ffiでGoでつくった共有ライブラリを呼び出すには以下の様になります。
「Hello,go! from Node.js」というNode.js内の文字列がGoのライブラリ側に 渡してGo側で表示します。
const ffi = require("ffi"); const mylib = ffi.Library('libtest', { 'colorprint': ['void', ['string']] }); mylib.colorprint("Hello,go! from Node.js");
以下のように動かします。
node test.js
node-ffiの実用的な応用編
node-ffiが実用的に使える可能性を示すようなものが何か出来ないかと あれ考え、以下にしました。OSX限定で済みません。
スクリーンキャスト用のキー履歴表示ツールをつくる
以下の記事で紹介されている今は開発が止まってしまったMacRubyをつかったスクリーンキャスト用のキー履歴表示ツールの Node.js版を作成することにしてみます。
独自のバイナリーを作り、安全確保
キーロガーなので、単純にnode.jsで動かすと、node.js自体をアクセス許可を与える必要があり、 試したあと、設定変更しないで、忘れていると潜在的なリスクが高いので、 別のOSXのアプリケーションとして作成し、これに許可を与えるようにするべきです。
そんな用途に打って付けなのがelectron-packagerです。 指定したアプリケーション名でElectronをアプリケーション内にパッケージしてくれます。
あ、Electronはnode.jsが入っているですよ。念のため。
その証拠に
ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 electron
とすれば、コマンドラインのREPLが動きます。(環境変数名がATOM_SHELLというもの今となっては 歴史を感じてしまいます。)
ただ、今回はフツーにElectronを使うことで、GUIまわりもWeb系の技法で実装します。
キーイベントを取得するCocoa APIを叩くには
NodObjCというモジュールを使います。このモジュールでNode.jsからCocoa APIが叩けるようになります。
NobObjCがnode-ffiを使っている箇所
というように、libojcを使って、C APIからObjective-CベースのCocoa APIにアクセスしています。
ブロック構文があつかえる
実は、OSXでキーイベントの取得に使うAPIはブロック構文を使うAPIであり、 開発が停止してしまったMacRubyでは出来ていたのですが、現在も開発が続いているRubyCocoaでは、ブロック構文が サポートされていない様です。そのため、前述のサイトのコードをRubyCocoaに移植?してもたぶん動かないと思います。
ちょっと話がそれましたが、NodObjCはブロック構文も使えます。
今回のコアとなるキーイベントを受取る部分のコードは以下の様になります。
const keyDownHandler = $(function(s,e){ console.log(e); //console.log(e('keyCode')); mainWindow.webContents.send('keydn', JSON.stringify({ characters: e('characters')('UTF8String'), flags: e('modifierFlags'), charactersIgnoringModifiers: e('charactersIgnoringModifiers')('UTF8String'), keyCode: e('keyCode')})); },['v',['?','@']]); $.NSEvent('addGlobalMonitorForEventsMatchingMask', $.NSKeyDownMask, 'handler', keyDownHandler );
成果物
まとめ
node.js側からのGoの呼び出しや、キー履歴表示ツールの事をもう少し詳細に書いても良かったのですが、 ここはNode.jsのアドベントカレンダーなので、別の機会に書くことにします。
関連記事
- ElectronはHTTP2が喋れないのか?
- lisp版ElectronのCeramicを動かした
- Electronで自分のIPアドレスをLAN内のiPhoneに通知するには
- Cocoa APIのAXIsProcessTrusterdとシステム環境設定のセキュリティとプライバシーの関係
- ElectronでWebGL(three.js)使ってデスクトップマスコット作った際に分かったこと
- WebGLを使ったElectronアプリをTravis CIで動かすまで
- Goオールスターズ2に行くので、gopheronをGo言語で動かせるようにした #websocket
- NAPI版のnode-ffi-napiを使ってNode.jsからPythonを動かす