C/C++によるWebAssemblyコードをJava Scriptから呼び出す(2/2)

Chrome / Firefox / Edge / Safari いずれにおいても、ブラウザ上でプログラムを実行したいときにはJavaScript が使われる。

JavaScriptが登場した当初、その役割はHTMLに飾りつけをする程度だった。

 

今では本当に立派になったなぁー。

ずっと見守っていたから「推し活」、「擬似子育て」感を感じるわ。

2000年頃、文系に質問されたけど「JavaとJavaScriptの違い」を答えられなくてゴメンよ……。

別に「推し」だからといってJavaScriptを使いこなせる訳じゃない。

前回はボタンクリックなどをトリガにしてwasmファイルを呼び出した。

今度はJavaScriptからWASM(C++)を呼び出しの幾つかの実験を行なった。

wasmの関数をJSから呼び出す方法(1/2)

注意する点は次の通り……と書いてある。

  • C++で実装する場合はextern “C”する
  • ビルド時のEXPORTED_FUNCTIONSの時だけ関数名の先頭にアンダースコアをつける
  • cwrapで関数を取り出すときはonRuntimeInitializedかmainが発火してから

JS側は次のようになる。

ここで、wasm関数を取り出すにはmodue.cwrap()を実行する。

cwrapの第一引数は取り出したい関数名、第二引数は返り値の型、第三引数は引数の型。

このcwrapの返り値はC/C++で実装したfunctionになっている。

分かりやすいサイトだな、と思いつつビルド&実行したら「Uncaught TypeError: module.cwrap is not a function」というエラーではまった……。

同じブログ見て同じようにエラーに出くわした人のブログも発見!

Emscripten 「module.cwrap is not a function」エラーではまる
C++のclassをEmscriptenでWebAssemblyに変換してJavaScriptのclassに変換するのを書いてみようと思い、...

結論として、ビルドオプションに次のオプションの追記で解決。

ビルドオプション 意味
EXTRA_EXPORTED_RUNTIME_METHODS ランタイムメソッドをExportする(JS側のModuleで使用するメソッドを列挙)
MODULARIZE JSファイル全体が関数となり、wasmの読み込みをより明示的に行える

ブラウザ上での実行結果は次の通り。

上手く動いた。

けど私には複雑でよく分からない……。

JavaScriptからC++を呼び出す(2/2)

もう少し簡素化できるっぽい。

コンパイラオプションに

を追加すると、cwrap を使わずに 「Module._関数」 でWASM関数を呼び出せるようになるらしい。

C/C++を使う目的は複雑な高速処理がやりたいからなので、今度はポインタを使ってみよう。

C/C++で定義した関数をJavaScript側で使用するには EMSCRIPTEN_KEEPALIVE をC/C++の関数名の前に追加する。

これによりEmscripten がコンパイル時に関数名を忘れない(関数名が生き続ける(keep-aliveする))。

また、JavaScript側(抜粋)では次のように呼び出す。

WebAssemblyに渡すデータはWebAssemblyのヒープに割り当てる必要がある。

JavaScriptのデータを、Module._mallocで確保した領域に転送する。

  1. まず_mallocでメモリを用意する
  2. C/C++で定義した関数名の先頭にアンダースコアをつけたModule._testFunctionを呼び出す
  3. 最後にJavaScript側で値を取得する

なお、ポインターから数値型配列取得はHEAP8、HEAPU8、HEAP16、HEAPU16、HEAP32、HEAPU32、HEAPF32、HEAPF64から行える。

ビルドオは次の通り。

「-s “MODULARIZE=1″」は不要。

結果は次のとおり。

期待通りに動いてる。

fopenは使えるのか?

JavaScriptをブラウザから使用する場合、セキュリティによりローカルへのテキストファイルへのアクセスが制限されている。

WebAssemblyでfileにアクセスするには preload または embedded という手法を用いなければならないらしい。

preloadをすることで、ブラウザー内に仮想的なファイルシステムをつくりアクセス可能になる……と書いてあった。

Emscripten Tutorial — Emscripten 3.1.53-git (dev) documentation

C++側のコードは次のようになる。

そして空の「test.txt」を作成しておき、次のようなコマンドでビルドする。

ログには正しくファイルが読み込まれた結果が出力された。

でもファイルには何も書かれてない。

決められたファイルの中身を読み込む事はできるけど、書き込んだり動的にファイルを読み込んだり……は出来ないっぽい。

memory access out of bounds

再帰関数が呼び出されて数回目に

で死んだ……。

再帰関数野デバッグは面倒臭いので一気にテンション下がったが、Javascript側からC++側に非情に大きいデータの受け渡しで、このエラーが発生したというブログを見つけた。

Unity:WebGLでメモリエラーに苦しんだ話 | Unity+AssetStoreおすすめ情報
はじめに Unity+WebGLでビッグなデータを扱うと発生するエラーの対策を書いてみました! Unity の

とりあえずビルドオプションを追加して詳細を見てみる。

to make it easier to diagnose things like this, using -s ASSERTIONS=2

と書いてあった。

Index out of bounds error with an array of certain size · Issue #12571 · emscripten-core/emscripten
I get this error in Firefox when running my wasm code: Uncaught RuntimeError: index out of bounds. In Chrome I get this: Uncaught RuntimeError: memory access ou...

さっそく「-s ASSERTIONS=2」をつけてビルド。

そして実行。

Aborted(stack overflow (Attempt to set SP to 0xfffffe20, with stack limits [0x00000000 – 0x00010000]). If you require more stack space build with -sSTACK_SIZE=)

スタックサイズが足りないからビルドオプションつけて増やせとの要求が表示された。

ビルドオプションに追加してみる。

すると次のようなエラー。

em++: error: INITIAL_MEMORY must be larger than STACK_SIZE, was 16777216 (STACK_SIZE=536870912)

エラーに従いINITIAL_MEMORYもビルドオプションに追加する。

とりあえず実行エラーは消えた。

適切な数値はどうやって知れるのかな……。

おわりに

やりたかった事は実現出来そうだけど、色々とハマってよく分かって無い部分も多い。

同じような問題でハマる人もいると思うので備忘録として載せておく。

タイトルとURLをコピーしました