JavaScriptの関数をC++に公開する


はじめに

前回は、C++の実行結果をhtmlに反映させました。今回は、JavaScriptの関数をC++に公開してC++内でhtmlを変更します。これによって複雑な計算をC++で行って直ぐにhtmlに反映させるようなことができてJavaScriptとC++の垣根が低くなるかと思います。

環境

  • windows 10
  • python:3.6.5
  • emcc:1.39.16
  • clang:11.0.0

Webアセンブリファイルを作成する

まずは、Webアセンブリ用のC++を作成してコンパイルをします。今回はC++のソースとコンパイルがそれぞれ変わっています。

C/C++ファイルの作成

例としてテキストボックスに数字を入れる関数(set_text)を想定します。C++ではextern void set_text(int x)で関数の定義だけしています。そして、使用したい関数(今回はmain)内でその関数を呼び出します。

cmain.cpp

#include <emscripten/emscripten.h>

extern "C" {
    extern void set_text(int x);
}

int main() {
    set_text(1111);
    return 0;
}

C/C++ファイルのコンパイル

Emscripten用のコマンドプロンプトを起動してコンパイルをします。コマンドプロンプトの起動などは前回を見てください。コンパイル時に-s SIDE_MODULE=1-O1のオプションを追加します。 -s SIDE_MODULE=1はサイドモジュールのオプションになります。-O1は、最適化オプションで必須ではないですがimportObjectの設定が手間なので入れます。

コマンド

em++ 入力C++ファイル -s SIDE_MODULE=1 -O1 -o 出力wasmファイル

ちなみにWASMは入れていません。デフォルトが1なのでいらないことに気が付きました。


>em++ cmain.cpp -s SIDE_MODULE=1 -O1 -o hello.wasm 

>

コマンドが終わると同フォルダ内にhello.wasmができているはずです。

wasmを呼び出すhtmlを作成する

前回のソースに加えてimportObjectの情報を追加して、C++のmain関数は戻り値を使用せずに単純に呼び出すだけにします。

importObjectの設定

前回と同じように確認した情報をimportObjectに設定していきます。その際に連想配列の関数にC++へ公開する関数の処理を書いていきます。今回はoutputに引数を格納しています。


    var importObject = { 
      wasi_snapshot_preview1: { proc_exit:args => console.log(arg) }, 
      env: {
        set_text: arg => document.getElementById("output").value = arg, 
      } };

インポート後の処理

インポート後の情報はobjに格納されているため、obj.instance.exports.main()でC++の関数を実行するだけにしています。

my_hello.html

<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>WebAssembly</title>
</head>
<body>
  <input type="button" id="test_view" value="表示" /><input type="text" id="output"/>

  <script>
    var importObject = { 
      wasi_snapshot_preview1: { proc_exit:args => console.log(arg) }, 
      env: {
        set_text: arg => document.getElementById("output").value = arg, 
      } };

    document.getElementById('test_view').addEventListener('click', () => {
      WebAssembly.instantiateStreaming(
        fetch('hello.wasm'), importObject
      ).then(obj => obj.instance.exports.main());
      }, false
    );      
  </script>
</body>
</html>

実行

確認用のサーバを再起動して上のhtmlを実行します。表示ボタンを押すとC++のmainが実行されていset_text()が実行されて与えていた1111が表示されました。うまくC++からJavaScriptの関数を実行することができました。

JQueryを使用してみる

JavaScriptをC++に公開しているのでJavaScriptのライブラリであるJQueryも公開できるはずと思いJQueryを使用してみました。

JQueryのダウンロード

npmを使えるので、npm経由でJQueryをダウンロードしました。

コマンド


npm install jquery

フォルダ内にnode_modulesができました。

JQueryをhtmlに適用

JQueryを使用してテキストを入れ替えます。先ほどの例に<script>$("#output").val(arg)を追加しました。

my_hello.html

<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>WebAssembly</title>
</head>
<body>
  <input type="button" id="test_view" value="表示" /><input type="text" id="output"/>

  <script>
    var importObject = { 
      wasi_snapshot_preview1: { proc_exit:args => console.log(arg) }, 
      env: {
        set_text: arg => $("#output").val(arg), 
      } };

    document.getElementById('test_view').addEventListener('click', () => {
      WebAssembly.instantiateStreaming(
        fetch('hello.wasm'), importObject
      ).then(obj => obj.instance.exports.main());
      }, false
    );      
  </script>
  <script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>
</body>
</html>

実験

サーバを立てて試してみたところ先ほどと同じように追加できました。JQueryも無事C++に公開することができました。

おわりに

JavaScriptの関数をC++に公開して実行できるようにしてみました。ぱっと見はそこまで恩恵はないように思いますが、処理速度が速くなっていたり、メモリ消費が抑えられていると恩恵は大きくなります。次回はそこら辺を実験してみようかと思います。