non vorrei lavorare

昔はおもにプログラミングやガジェット系、今は?

NAPI版のnode-ffi-napiを使ってNode.jsからPythonを動かす

こんばんは。夜遅くまで子供達が起きてしまい、朝叩き起こしてもなかなか起きない悩みが一向に解決しない@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}`)
}

実行結果

youtu.be

おわりに

背景で触れたようにCocoa専用なので、今度のCatalinaではPythonが標準で入らなくなる模様。 しかしながら、Node.jsはいまでも確かPython2系が必要なはずで、今回のアプローチでも Catalina環境でもNode.jsを利用する場合、Pythonを入れる必要があり、無駄にはならないと思われる。

参考資料

blog.hiroppy.me

関連記事

8年前の記事

5年前の記事