Emscriptenを使ったC/C++の関数のエキスポート方法まとめ

## TL;DR; 

Web/Node.js で使うことだけ考えるなら、[Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html) が一番楽です。

# いくつか手段があるのでまとめます

C/C++ のコードを WebAssembly に変換する際に便利なツール [Emscripten](https://emscripten.org/) ですが、関数をエキスポートするにはいくつかの方法があります。この記事では、そのやり方をまとめます。

## main 関数をエキスポートする場合

何もすることはありません。main を定義し、通常の C/C++ のコードのようにコンパイルするだけです。main 関数は自動的にエキスポートされ、ロード時に自動実行されます。

もし自動実行したくない場合は、[`-s 'INVOKE_RUN=0'`](https://github.com/emscripten-core/emscripten/blob/master/src/settings.js#L63)を指定します。

~~~sh
% emcc -o hello.html -s 'INVOKE_RUN=0' hello.c
~~~

## main 関数をエキスポートしない / main 関数が存在しない場合

main 関数をエキスポートしない場合、もしくは存在しない場合は、エキスポートする関数を明示する必要があります。これは [Emscripten が dead code elimination（JS では tree shaking と呼ばれている行為）を行うから](https://emscripten.org/docs/getting_started/FAQ.html#faq-dead-code-elimination)です。main 関数がエキスポートされる場合、main 関数から到達可能なもののみがWASMに出力されます。

main 関数がエキスポートされない場合、特に main 関数が存在しない場合は、すべての関数が削除され、ユーザーコードが存在しない wasm ファイルが出力されます。これを避けるために、以下のどちらかの方法でエキスポートするシンボルを指定する必要があります：

- コマンドライン引数で指定する
- コード中にアノテーションして指定する

### コマンドライン引数で指定する方法

[`EXPORTED_FUNCTIONS`](https://github.com/emscripten-core/emscripten/blob/master/src/settings.js#L571) にエキスポートする関数のリストを指定できます。

例えば次のようなコードがあったとします：

~~~C
#include <stdio.h>
int add(int a, int b){
  return a + b;
}
int sub(int a, int b){
  return a - b;
}
int main(int arg, char** argv){
  printf("Hello, world!");
}
~~~

このコードをオプションを指定せずにコンパイルすると、`main`関数のみがエキスポートされた wasm ファイルが作られます。`add` 関数も　`sub` 関数もエキスポートされないばかりか、wasm ファイルに含まれないため、これらの関数をライブラリ的に利用することはできません。

こんな時、`EXPORTED_FUNCTIONS` を指定することで `add` と `sub` のみがエキスポートされた wasm ファイルを作ることができます：

~~~
% emcc -o add_sub.js -s "EXPORTED_FUNCTIONS=['_add', '_sub']" add_sub.c
~~~

値には、エキスポートする関数名を表す文字列を要素とする配列を指定します。

関数名には、`_` をプレフィックスとしてつけます。例えば `add` をエキスポートするときは `_add` とします。

`-s` オプションは [settings.js](https://github.com/emscripten-core/emscripten/blob/master/src/settings.js)の値を設定するオプションです。このオプションの値は`"`でエスケープされていなければなりません。そのため、`"`と`'`の入り乱れた、かなり手で入力するには厳しい値を入力することになります。

エキスポートするシンボルが少ない場合は、この方式でもいいかもしれません。個人的な経験では、3 つ以上のシンボルをエキスポートするときは、次に説明するアノテーションを使った方式の方がストレスが少ないように感じました。

### `EMSCRIPTEN_KEEPALIVE` でアノテートする方法

次のように `EMSCRIPTEN_KEEPALIVE` をエキスポートしてほしい関数の前につけておくと、その関数は dead code elimination の対象から外れ、エキスポートされます。

~~~c
#include <emscripten.h>

int EMSCRIPTEN_KEEPALIVE add(int a, int b){
  return a + b;
}
~~~

`EMSCRIPTEN_KEEPALIVE` は `emscripten.h` で定義されています。このヘッダファイルは、Emscripten でコンパイルする際には解決されますが、それ以外の場合はコンパイルエラーの原因となります。
`EMSCRIPTEN_KEEPALIVE` を定義すれば、Emscripten 以外の環境でのコンパイルエラーは避けられます。この単純な実装は、次のようになるでしょう。なお[`__EMSCRIPTEN__` によって、Emscripten でコンパイルされているかどうかが判別できます](https://emscripten.org/docs/compiling/Building-Projects.html#detecting-emscripten-in-preprocessor)。

~~~c
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif

int EMSCRIPTEN_KEEPALIVE add(int a, int b){
  return a + b;
}
~~~

### Embind を使う方法

以上でプリミティブな値を返す関数をエキスポートすることはできるようになりました。Emscripten は文字列の変換もよしなに行ってくれるため、多くのユースケースをカバーできると思います。

一方でユーザ定義型、特に C++ のクラスを JS からも透過的に使いたいという要求もあるでしょう。そんな時に便利なのが [Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#embind) です。これは JS の関数やクラスを自動的に定義することで、アノテーションした関数やクラスにを　JS からの透過的な操作を可能にするツールです。例えば次のような関数と、クラスがあったとしましょう。2 次元平面上の点と、その点から原点 (0, 0)　までの距離を測るコードです：

~~~c++
int abs(int value){
  return value >= 0 ? value : -value;
}

long norm(int x, int y){
  return abs(x) + abs(y);
}

class Point{
public:
  Point(int x, int y) : x(x), y(y)  {
  }
  long norm() const {
    return ::norm(x, y);
  }
  long length() const {
    return norm();
  }

private:
  int x;
  int y;
};
~~~

ここで定義されている `Point` クラスを JS でも使いたい、ラッパーなんて書きたくないという要求があるのは想像できるでしょう。

~~~JavaScript
const mod = await someWASMLoadingFunction(); // 先のコードが WASM 化されているものを、何がしかの方法でロード

const p = new mod.Point(1, 2);
const len = p.length;
~~~

そんな時が Embind の使いどきです。次のようにエキスポートする関数、クラスを記述することで、コンパイル時にグルーコードが自動生成され、透過的に（つまりラッパーを書かずに）C++ で定義したコードを JS から利用できます。

~~~C++
#ifdef __EMSCRIPTEN__
#include <emscripten/bind.h>
#endif
// snip

#ifdef __EMSCRIPTEN__
EMSCRIPTEN_BINDINGS(myModule)
{
  emscripten::class_<Point>("Point")
      .constructor<int, int>()
      .function("norm", &Point::norm)
      .property("length", &Point::length);
}
#endif
~~~

[`EMSCRIPTEN_BINDINGS`](https://emscripten.org/docs/api_reference/bind.h.html#_CPPv419EMSCRIPTEN_BINDINGS4name) のブロック内に、JS と C++ のクラス間の対応関係を記述します。

まず [`_class`](https://emscripten.org/docs/api_reference/bind.h.html#_CPPv46class_) オブジェクトを作成して、JS のクラスと C++ のクラスの対応関係を定義します。

あとは [`constructor` メソッドでコンストラクター](https://emscripten.org/docs/api_reference/bind.h.html#_CPPv4NK6class_11constructorEv)を、[`function` メソッドでメソッド](https://emscripten.org/docs/api_reference/bind.h.html#_CPPv4NK6class_8functionEv)を、[`property` メソッドで属性の対応](https://emscripten.org/docs/api_reference/bind.h.html#_CPPv4NK6class_8propertyEv)を定義します。

これらのシンボルは `emscripten/bind.h` に定義されています。これをインクルードした上で、`--bind` オプション付きでコンパイルすると、上記の対応に基づいた JS クラスが自動的に作成されます。

~~~
% emcc --bind -o point.js point.ccp
~~~

なお [WebAssembly の GC 対応](https://github.com/WebAssembly/gc)は議論中で、実装はまだありません。また [weak reference に対応していない環境](https://www.chromestatus.com/feature/5166497248837632)もあります。そのため、使い終わったオブジェクトは明示的に `delete` メソッドを読んでメモリを解放する必要があります。

## まとめ

* クラスをエキスポートするなら、Embind を使う
* 数をエキスポートするなら、`EMSCRIPTEN_KEEPALIVE` をつけるか、Embind を使う

と良さそうです。関数の場合、どちらを使っても良さそうです。パフォーマスオーバヘッドについてはわかりませんが、Embind の方が使い勝手は良いように感じています。私は、メモリの解放に気を使わなければならない点を除けば、WASM であること全く気にしなくてもいい点が気に入っています。

メモリの解放に関しては、Reference type などの仕様に関する議論が進み、ツールとブラウザの実装によって、いずれ解決する問題のようにも感じています。