non vorrei lavorare

2020年度からの小学校プログラミング教育の必修化を親として迎えるブロガーの書く、子供との日常

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年前の記事