背景
C/C++ コードを Python からポータブルな形で呼び出したい...
WASI + wasmer-python でやってみます.
malloc/new しなかったり, C++ STL 使わないような場合は Emscripten 利用も検討ください.
wasmer-python と emcc で C/C++ コードを wasm にして python でポータブルに動かすメモ(制約多い)
https://qiita.com/syoyo/items/6f0b74014e55ecf0a02b
制約
-
main
を用意し,main
経由で処理しないといけない - C++例外や thread は使えない
- Python 界とは直接はやりとりできず, 画像データを処理したいときなどは, ファイル経由でアクセスしないとだめっぽい(WASI の Rust runtime では stdin/stdout 上書きできるようだが, wasmer-python ではできない)
Emscripten に対しての利点
- malloc/new とか, C++ STL(+ libc)関連がある程度使えるので, より汎用な C/C++ コードを動かすことができる
方法
まず, wasi-sdk で C/C++ コードを WASI 形式(?) の WASM にします.
#include <cstdio>
#include <cstdlib>
int main(int argc, char **argv)
{
printf("argc %d\n", argc);
for (size_t i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
以下のような感じでコンパイルしておきます.
/home/syoyo/work/wasi-sdk/wasi-sdk-16.0/bin/clang++ --sysroot=/home/syoyo/work/wasi-sdk/wasi-sdk-16.0/share/wasi-sysroot -fno-exceptions -o binding.wasm binding.cc
wasmer-python で実行
そのままだと wasi_snapshot_preview1
の fd_close
とかのシンボルが見つからないエラーになります.
WASI 用のランタイムが用意されていますので, それを import するようにします.
手順としては, StateBuilder で main
実行の情報(プログラム名, 引数, 環境変数, アクセスできるフォルダ)を設定します.
StateBuilder の情報と, ランタイムは WASI のバージョンに依存するので, WASI のバージョンから import_object を作り, _start
を invoke します!
from wasmer import engine, wasi, ImportObject, Function, Store, Memory, MemoryType, Module, Instance
from wasmer_compiler_cranelift import Compiler
store = Store(engine.Universal(Compiler))
wasm_bytes = open('binding.wasm', 'rb').read()
module = Module(store, wasm_bytes)
print(module)
wasi_version = wasi.get_version(module, strict=True)
print("WASI version", wasi_version)
wasi_env = \
wasi.StateBuilder('wasi_test_program'). \
argument('--myargs'). \
environment('BORA', 'true'). \
environment('DORA', 'false'). \
map_directory('the_host_current_dir', '.'). \
finalize()
import_object = wasi_env.generate_import_object(store, wasi_version)
instance = Instance(module, import_object)
# _start に引数を与えても反映されないので注意
result = instance.exports._start()
argc 2
argv[0] = wasi_test_program
argv[1] = --myargs
データのやりとり
stdin/stdout 使えないので, 小規模なデータであれば環境変数 or 引数で指定, 画像データなどの場合は一度ファイルに落としてやりとり, となるでしょう.
(PIPE とか使えるんじゃろか?)
複数インスタンス起動したい場合はファイル名を変えたり, map_directory
で調整でしょうか.
main 返り値
wasmer-python では, 0 以外の場合は例外を出します
RuntimeError: WASI exited with code: 3
返り値取得したいときは以下のような感じになるでしょうか.
try:
instance.exports._start()
except RuntimeError as e:
print("err_code", str(e).split()[-1])
よりきちんとエラーメッセージをパースするなら parse
https://pypi.org/project/parse/ を使うとよいでしょう.
おまけ. wasmtime で動かす
コマンドラインから動かせました
Python から subprocess で起動とかでもいいかも.