WebAssemblyでC++とJavaScript間のやり取り

# はじめに

[昨日の私の記事](https://qiita.com/soramimi_jp/items/0cb28436f67bb42690ee)では、Qt for WebAssemblyを使ったGUIアプリの開発方法を紹介しました。今日の記事ではQtは一切関係ありません。C++とJavaScriptとの間で、関数を呼び合ったりデータを受け渡しする方法を紹介します。

Ubuntu 18で開発する想定です。昨日の記事を参考にして、開発環境をインストールしてください。Qtは必要ありませんが、統合開発環境のQt Creatorを使うと、C++開発に最適化されたテキストエディタの他、シンボル名補完や、宣言と定義の間のジャンプ、リファクタリング機能も利用できますので、あると便利です。もちろん使い慣れたテキストエディタで構いません。

## 最初のアプリ

基本的な手順は、Mozillaのサイトに全部書いてあります。

https://developer.mozilla.org/ja/docs/WebAssembly/C_to_wasm

[昨日の記事](https://qiita.com/soramimi_jp/items/0cb28436f67bb42690ee)の手順に従って、環境変数を定義し、Emscriptenの動作確認をしておきます。

`source ~/emsdk/emsdk_env.sh`
`em++ -v`

適当な作業用ディレクトリを作って、最初のソースファイルを作成します。

``` hello.c
#include <stdio.h>

int main()
{
	printf("Hello, world\n");
	return 0;
}
```

ビルドします。

`emcc hello.c -s WASM=1 -o hello.html`

上の例ではC言語のソースをコンパイルしました。C++であれば次のようにします。

`em++ hello.cpp -s WASM=1 -o hello.html`

成功したら3つのファイルが生成されます。それらをウェブサイトのディレクトリにコピーします。

`cp hello.js /var/www/html/wasmtest/`
`cp hello.html /var/www/html/wasmtest/`
`cp hello.wasm /var/www/html/wasmtest/`

ウェブブラウザでF12キーを押して、開発者機能を開き、コンソール出力を表示したら、 http://localhost/wasmtest/hello.html にアクセスします。コンソールに「Hello, world」が表示されたら成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/2ea565fe-6632-23b1-1827-4d852911c373.png)

## HTMLの改造

hello.html を index.html にコピーして、これを編集します。WebAssemblyモジュールをロードする以外のヴィジュアル要素を全て取り除きます。

``` index.html
<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>wasmtest</title>
  </head>
  <body>
    <script type='text/javascript'>
      var Module = {
        preRun: [],
        postRun: [],
        print: (function() {
          return function(text) {
            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
            console.log(text);
          };
        })(),
        printErr: function(text) {
          if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
          //console.log(text);
        },
        totalDependencies: 0,
        monitorRunDependencies: function(left) {
          this.totalDependencies = Math.max(this.totalDependencies, left);
        }
      };
      console.log('Downloading...');
      window.onerror = function(event) {
        // TODO: do not warn on ok events like simulating an infinite loop or exitStatus
      };
    </script>
    <script type="text/javascript" src="hello.js"></script>
  </body>
</html>
```

## C++側ソースを編集

C/C++によるWebAssemblyのプログラムは、初めの方でお決まりのヘッダファイルをインクルードします。

``` .cpp
#include <emscripten.h>
```

C言語のまま進めても問題ありませんが、将来のためC++にしておきます。

`mv hello.c hello.cpp`

コンパイルするには、前述の手順のとおり、`emcc`の代わりに`em++`コマンドを使用します。

`em++ hello.cpp -o hello.js`

これを実行すると`hello.js`と`hello.wasm`が生成されますので、ウェブサイトのディレクトリにコピーします。コンパイルしてインストールするためのスクリプトを書いておくと便利です。

``` .sh
#!/bin/sh
em++ hello.cpp -o hello.js
cp hello.js /var/www/html/wasmtest/
cp hello.wasm /var/www/html/wasmtest/
```



### JavaScriptからC/C++の関数を呼び出す方法

C言語の関数として定義します。C++の場合は`extern "C"`が必要です。また、`EMSCRIPTEN_KEEPALIVE`というのを付けておきます。ここでは例として2つの関数を用意します。

``` .cpp
extern "C" void EMSCRIPTEN_KEEPALIVE initialized()
{
	puts("initialized");
}

extern "C" void EMSCRIPTEN_KEEPALIVE clicked1()
{
	puts("clicked1");
}
```
JavaScriptからは'`_`'をつけた関数名で呼び出しします。

``` .js
Module._initialized();
```
または、単純に以下のように書いても動作します。

``` .js
_initialized();
```

これを踏まえて、HTMLの最後の方に、次のように書いておきます。

``` index.html
    <script>
		Module.onRuntimeInitialized = function(){
		  _initialized();
		}
    </script>
    <button onclick="_clicked1()">OK</button>
```

モジュールの初期化が終わるまで待って initialized() 関数を呼んでいます。また、ボタンが押されたら clicked1() 関数を呼んでいます。

これを実行すると、モジュールの初期化が完了したら initialized と表示され、次に main() 関数が実行されて Hello, world が表示されます、ボタンが押される度に clicked1() が呼ばれていることも確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/23bbfb77-8d9a-8cf3-c19f-2f63862e2832.png)

### HTML要素の値を取得する

例えば、次のようなHTMLがあるとします。

``` .html
<input id="input1" value="hogehoge">
```

C++から次のような感じに`value`の値を取得したいです。

``` .cpp
std::string value = getElementValue("input1");
```

これをやるために`getElementValue`関数を作ってみます。

``` .cpp
#include <string>

//（略）

EM_JS(void *, getElementValue_, (char const *id),
{
	var e = document.getElementById(UTF8ToString(id));
	var str = e.value;
	var len = lengthBytesUTF8(str) + 1;
	var heap = _malloc(len);
	stringToUTF8(str, heap, len);
	return heap;
});

std::string getElementValue(std::string const &id)
{
	void *p = getElementValue_(id.c_str());
	std::string s((char const *)p);
	free(p);
	return s;
}
```

C++のソースの中にJavaScriptを埋め込むために、`EM_JS`というキーワードを使っています。`EM_JS(戻り型, 関数名, 仮引数, { スクリプトコード });`の様に定義します。JavaScriptからC++にデータを返すために、`malloc`関数でヒープからメモリを確保しています。それを呼び出す側のC++プログラムで、文字列オブジェクトを作成したら、`free`関数でメモリを開放しています。

### HTML要素の値を設定する

前項とは逆に、値の設定をします。C++中にインラインでJavaScriptを記述するために`EM_ASM`というキーワードを使っています。これに渡した引数は、`$0`、`$1`の様にしてJavaScriptから使用できます。UTF-8文字列をJavaScriptの文字列に変換するために`UTF8ToString()`という関数を使っているところがポイントです。

``` .cpp
void setElementValue(std::string const &id, std::string const &value)
{
	EM_ASM({
		var e = document.getElementById(UTF8ToString($0));
		e.value = UTF8ToString($1);
	}, id.c_str(), value.c_str());
}
```

次のようにすると、ボタンを押すとinput1要素の値が書き換わります。

``` .cpp
extern "C" void EMSCRIPTEN_KEEPALIVE clicked1()
{
	setElementValue("input1", "Hello, world");
}
```

### HTML要素の内部HTMLに書き込む

次のようなタグがあるとします。

``` .html
<div id="contents"></div>
```

この`div`タグの`innerHTML`に書き込みます。C++中にインラインでJavaScriptを埋め込み、要素の`innerHTML`を変更しています。

``` .cpp
void setElementInnerHTML(std::string const &id, std::string const &html)
{
	EM_ASM({
		var e = document.getElementById(UTF8ToString($0));
		e.innerHTML = UTF8ToString($1);
	}, id.c_str(), html.c_str());
}
```

次のようにすると、ボタンを押すとcontents要素内のHTMLが書き換わります。

``` .cpp
extern "C" void EMSCRIPTEN_KEEPALIVE clicked1()
{
	setElementInnerHTML("contents", "<p>Hello, world</p>");
}
```