Emscripten(C/C++) と JavaScript 間の関数呼び出し方法 まとめ
ここのところ、Emscriptenを使ったアプリ開発に関して色々と試していました。今回は、C/C++ と JavaScript間の関数呼び出しや値の受け渡し方法についてまとめたいと思います。
JavaScriptからC/C++のコードを呼び出す
(C/C++側)オプティマイザに消されないようにする
C/C++のコンパイル時に、オプティマイザが関数を削除してしまわないように、コンパイラに指定する必要があります。
以下のように、EXPORTED_FUNCTIONSで個別に関数名(先頭に’_’がつきます)を指定します。また、C言語の関数とするためにC++ないで定義する場合には「extern “C”」が必要です。
1 2 3 |
-s EXPORTED_FUNCTIONS="['_Init','_Hello'] |
もしくは、 -s EXPORT_ALL=1 としてすべて含めることもできます。ただし、何かのライブラリをリンクしていたりすると大きくなるため、あまりおすすめできません。
コンパイル時に指定する以外にも、ソースコードの方から指定することも可能です。「emscripten.h」をインクルードし、エクスポートしたい(削除したくない)関数の前に「EMSCRIPTEN_KEEPALIVE」をつけます。以下のような感じです。
1 2 3 4 5 6 7 8 9 10 11 |
#include <emscripten.h> //cppから呼ぶこと前提。Cと混在なら #ifdef __cplusplus 等が必要 extern "C" { EMSCRIPTEN_KEEPALIVE void Init(); EMSCRIPTEN_KEEPALIVE void Hello(const char *name); } |
こちらで指定しておけば、コンパイルオプションを書き換える必要はなくなります。数が多い時は、ソースコードに書いたほうが便利かもしれません。
(JavaScript側) ccall または cwrap を使う
C/C++側の関数を直接呼び出すときは、Moduleのccall関数を使います。以下のような感じです。
1 2 3 4 5 6 7 |
Module.ccall( 'Hello', // 関数名(先頭の'_'は要らない) '', // 戻り値の型名(ない場合は空) ['string'], // 引数の型名の配列 ['テスト']); // 引数の配列 |
型名は、”number”か”string”になります。”string”にした場合、JavaScriptの文字列を渡すことができ、C言語側では「const char*」型で受け取ることができます。(ドキュメントによると”array”も使えるらしいですが、まだ試していません)
cwrap関数を使い、JavaScriptの関数オブジェクトにすることもできます。頻繁に使用する関数は、関数オブジェクト化しておくと良いと思います。
1 2 3 4 5 6 7 8 |
var Hello = Module.cwrap( 'Hello', // 関数名(先頭の'_'は要らない) '', // 戻り値の型名(ない場合は空) ['string'], // 引数の型名の配列 ); Hello('てすと'); |
C/C++からJavaScriptのコードを呼び出す
emscriptenでは、EM_ASMブロックを内に、インラインアセンブラのようにJavaScriptのコードを直接書き込むことができます。注意としては、インライン内では「”」は使えません。そのため必ず「’」を使って下さい。
1 2 3 4 5 |
EM_ASM( // JavaScriptのコード ); |
引数がある場合はEM_ASM_を使います。
1 2 3 4 5 |
EM_ASM_({ console.log('value: ' + $0); }, 100); |
JavaScript側では、$0, $1 … のように引数を順番に受け取ることができます。
文字列を渡す場合は、char型のポインタ(おそらくUTF8形式)を渡します。インラインから文字列を得るためには「Pointer_stringify」関数を使用します。なお、変換した文字列は一度変数に入れないとうまく動かないことがありましたので注意が必要です。
1 2 3 4 5 6 7 8 |
char* text = "Test Text"; EM_ASM_({ var s = Pointer_stringify($0); console.log(s); }, text); |
バイト配列を渡す場合は、ポインタを渡します。その際、長さも渡しておくと良いでしょう。そして、インラインから以下のようにしてTypeArrayを作成してアクセスします。
1 2 3 4 5 6 7 |
unsigned char* bytes = malloc(128); EM_ASM_({ var b = new Uint8Array(Module.HEAPU8.buffer, $0, $1); }, bytes, 128); |
この方法を使えば、C/C++側で処理した画像を、JavaScript側のCanvas等に書き戻すことも可能です。調べたところこの方法しか見つかりませんでしたが、メモリ効率がどうなのかは不明です。
JavaScriptから値を返す場合には、EM_ASM_INT(整数を返す)や EM_ASM_DOUBLE(浮動小数点数を返す)を使用します。使い方は、EM_ASM_と同じです。引数を渡さないとエラーになるようです。
この他にも、JavaScriptの関数をC/C++側から直接呼ぶようにすることもできるようです。少々面倒なため、詳細はこちら。