こんばんは。夜遅くまで子供達が起きてしまい、朝叩き起こしてもなかなか起きない悩みが一向に解決しない@kjunichiです。
背景
たまーに、流行のGitHub見て転職案内してくれるサイトに登録してるので、 KeyCast.js見ました!なんてメールをいただくことがありますが。 AIからのメールなのはバレバレでもメンタル豆腐なので、この数年メンテしておらず心苦しくになることがあるので、 重い腰を上げて対応しようとしたら、NodObjCが不安定すぎて、別の方法を模索している際のメモ。
NodObjCの代わりにPyObjcを使ってみるための準備
NodObjCがN-API版のnode-ffi-napiではサポートされていない模様。 しかし、node-ffi-napiから直接libobjcを叩く際にはBlocksを引数にとるCocoa APIの扱いが不明なので、 PyObjCを使って回避を試みる。
ffi-napiを使ってNode.jsからPythonを動かした
const ffi = require('ffi-napi') const ref = require('ref-napi') const voidPtr = ref.refType(ref.types.void); const libpython2 = ffi.Library('libpython2.7', { 'Py_SetProgramName': [ 'void', [voidPtr] ], 'Py_Initialize': [ 'void', [] ], 'PyRun_SimpleString': [ 'int', ['string'] ], 'Py_Finalize': [ 'void', [] ] }) libpython2.Py_SetProgramName(null) libpython2.Py_Initialize() const pyscript = `print("Hello world!\\n")` libpython2.PyRun_SimpleString(pyscript) libpython2.Py_Finalize()
Node.jsでスレッドを作り、Pythonを動かす
Node.jsとPythonとのやり取りはHTTP越しで行うことを考えている。 Node.jsに限らず、FFIをスクリプト系言語で行った方ならCのヘッダの関数や構造体の型定義をちまちま書く作業の 手間を想像することはたやすいかと思われる。
Node.js側でPythonのスクリプトを起動するだけして、その後のやり取りはHTTP越しに行うことにすれば、Pythonの処理系にPIPで 追加モジュールのインストールの必要もなく、Node.js側も標準のモジュールで通信が行える。
ワーカ用のJavaScriptコード
const ffi = require('ffi-napi') const ref = require('ref-napi') const voidPtr = ref.refType(ref.types.void); const libpython2 = ffi.Library('libpython2.7', { 'Py_SetProgramName': [ 'void', [voidPtr] ], 'Py_Initialize': [ 'void', [] ], 'PyRun_SimpleString': [ 'int', ['string'] ], 'Py_Finalize': [ 'void', [] ] }) libpython2.Py_SetProgramName(null) libpython2.Py_Initialize() const pyscript = `str=raw_input()\nprint("Hello world!\\n")` libpython2.PyRun_SimpleString(pyscript) libpython2.Py_Finalize()
メインスレッド用のJavaScriptコード
const { Worker, isMainThread, workerData } = require('worker_threads') const { resolve } = require('path') let current = 0 const counter = (title, cnt) => { console.log(`| ${title} |: ${cnt}`) } if (isMainThread) { console.log('Main Thread') for (let i = 0; i < 1; i++) { // 相対パスはダメ // この場合(__filename)はworkerも同じこのファイルを参照する // 第二引数はグローバルパラメーター new Worker(resolve("python.js"), { workerData: i }) } setInterval((title) => { counter(title, ++current) }, 1000, 'MainThread') } else { console.log(`worker: ${workerData}`) setInterval((title) => { counter(title, ++current) }, 1000, `worker: ${workerData}`) }
実行結果
おわりに
背景で触れたようにCocoa専用なので、今度のCatalinaではPythonが標準で入らなくなる模様。 しかしながら、Node.jsはいまでも確かPython2系が必要なはずで、今回のアプローチでも Catalina環境でもNode.jsを利用する場合、Pythonを入れる必要があり、無駄にはならないと思われる。