【WebAssembly】C/C++で実装した関数をJSから呼び出す

f:id:shogonir:20170514025514p:plain

目次

  1. 本記事の目的
  2. C/C++で呼び出したい関数を実装する
  3. emccのExportedFunctionsオプションを使ってビルド
  4. JSからwasmを読み込んで関数を呼び出す
  5. まとめ

 

1. 本記事の目的

本記事では、C/C++で実装した関数をJSから呼び出す方法を紹介します。
例として、int型の引数を2つとって和を返すadd(int, int)を実装します。

GitHubにソースを上げました。
wasm-sample/02-wasm-exported-functions at master · shogonir/wasm-sample · GitHub

 

2. C/C++で呼び出したい関数を実装する

add(int, int)をCとC++で実装します。

2.1. C言語で実装する

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

2.2. C++で実装する

C++の場合はマングリングを回避するためにextern “C"を忘れないようにしてください。

extern "C" {
    
    int add(int a, int b) {
        return a + b;
    }
}

 

3. emccのEXPORTED_FUNCTIONSオプションを使ってビルド

dockerコンテナ内でC/C++からwasmをビルドするため、cpp2wasm.shを作成します。

emcc add.c \
    -s WASM=1 \
    -s "MODULARIZE=1" \
    -s "EXPORTED_FUNCTIONS=['_add']" \
    -o add.js

EXPORTED_FUNCTIONSの右辺はシングルクォートの文字列のリストです。
実装した関数の先頭にアンダースコアをつけましょう。
C++の場合はadd.cの部分をadd.cppに変えるだけで大丈夫です。

上記のcpp2wasm.shをdockerコンテナ内で実行するcompile.shを作成します。

docker run --rm -t -v $(pwd):/src gifnksm/emscripten-incoming sh cpp2wasm.sh

この状態でcompile.shを実行するとadd.js, add.wasmが生成されるはずです。

 

4. JSからwasmを読み込んで関数を呼び出す

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <title>modularize</title>
  </head>
  <body>
    <script src="add.js"></script>
    <script>

var module;

fetch('add.wasm')
  .then(response => response.arrayBuffer())
  .then(buffer => new Uint8Array(buffer))
  .then(binary => {
    var moduleArgs = {
      wasmBinary: binary,
      onRuntimeInitialized: function () {
        var add = module.cwrap('add', 'number', ['number', 'number']);
        console.log(add(2, 3));
      }
    };
    module = Module(moduleArgs);
  });

    </script>
  </body>
</html>

module = Module()とした後、onRuntimeInitializedコールバック内で関数を取り出して実行しています。

関数を取り出すにはmodue.cwrap()を実行します。
cwrapの第一引数は取り出したい関数名、第二引数は返り値の型、第三引数は引数の型です。
このcwrapの返り値はC/C++で実装したfunctionになっています。

 

5. まとめ

今回はwasmの関数をJSから呼び出す方法を紹介しました。
下記を注意してください。

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