目次
1. この記事の目的
この記事では、wasm側で作成したインスタンスをJSに渡す方法を紹介します。
以下のような例で、サンプルコードも載せながら説明します。
C++でPointという名前のx, y-座標系上の点を表すクラスを定義します。
Pointがもつ情報はx, y座標と、これだけだと単純すぎるのでidをもたせます。
wasmに角度を渡すと、指定された角度に対応する単位円上の点を返します。
idは自動でユニークなのIDを振るのが面倒だったので固定で4423にします。
GitHubにソースを上げました。
github.com
2. JSに渡したいクラスをC++で定義する
Pointクラスを定義します。
まずはヘッダーからです。(point.h)
class Point { public: Point(float x, float y); public: float x; float y; short id; };
次に実装です。(point.cpp)
#include "point.h" Point::Point(float x, float y): x(x), y(y) { this->id = 4423; }
3. wasmのソースコードを実装する
下記の2つの関数を実装します。
- Point* angleToPoint(float angle)
- float型の角度を受け取って、指定された角度に対応する単位円上の点を返す
- void freePoint(Point*)
- 受け取ったポインタで確保しているPointインスタンスの領域を解放
実際のソースコードは下記のようになりました。
#include <cmath> #include "point.cpp" extern "C" { Point * angleToPoint(float theta) { float radian = theta * 3.1415926 / 180; float x = std::cos(radian); float y = std::sin(radian); Point * point = new Point(x, y); return point; } void freePoint(Point * point) { delete point; } }
4. JS側でポインタからインスタンスを読み込む
wasmから受け取ったポインタをもとに、インスタンスの情報を読み込む処理は下記のようになります。
function pointerToPoint(pointer) { const POINT_OFFSET_X = pointer / 4; const POINT_OFFSET_Y = pointer / 4 + 1; const POINT_OFFSET_ID = pointer / 2 + 4; var point = {} point.x = module.HEAPF32[POINT_OFFSET_X]; point.y = module.HEAPF32[POINT_OFFSET_Y]; point.id = module.HEAP16[POINT_OFFSET_ID]; return point; }
この処理について解説します。
wasmのメモリへアクセスする方法については、こちらの3章を先に参照してください。
まずX座標ですが、これはHEAPF32の先頭からpointer / 4番目に格納されています。
これはfloat型が4byteであるためです。
次にY座標です、こちらはX座標の次にあるのでHEAPF32のpointer / 4 + 1番目です。
最後にidですが、これはHEAP16のpointer / 2 + 4番目になります。
idはshort型なのでHEAP16にアクセスする必要があり、pointer / 2から8byteはx, yが格納されていますので、2byteのshort型4つ先にidが格納されているはずです。
最後に読み込んだ情報をpointというObjectに格納して返しています。
5. HTMLからwasmを読み込んで実行する
wasmを読み込んでじっこうするHTMLは以下のようになります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>share instance</title> </head> <body> <script src="share.js"></script> <script> var module; function pointerToPoint(pointer) { const POINT_OFFSET_X = pointer / 4; const POINT_OFFSET_Y = pointer / 4 + 1; const POINT_OFFSET_ID = pointer / 2 + 4; var point = {}; point.x = module.HEAPF32[POINT_OFFSET_X]; point.y = module.HEAPF32[POINT_OFFSET_Y]; point.id = module.HEAP16[POINT_OFFSET_ID]; return point; } fetch('share.wasm') .then(response => response.arrayBuffer()) .then(buffer => new Uint8Array(buffer)) .then(binary => { var moduleArgs = { wasmBinary: binary, onRuntimeInitialized: function () { var angleToPoint = module.cwrap('angleToPoint', 'number', ['number', 'number']); var freePoint = module.cwrap('freePoint', null, ['number']); var pointer = angleToPoint(30); var point = pointerToPoint(pointer); console.log(point); freePoint(pointer); } }; module = Module(moduleArgs); }); </script> </body> </html>
onRuntimeInitializedのタイミングで、cwrapを用いて関数を取り出しています。
角度を30度に設定して、Pointのインスタンスのポインタをpointerに格納します。
それをpointerToPoint(number)に渡してpointにオブジェクトを格納します。
インスタンスの情報を読み込んで、pointerが不要になったらfreePoint()を実行するのを忘れないようにしてください。
これを忘れるとメモリリークになってしまいます。
もしメモリリークをJSで気にしたくない場合、次の方法が役に立つかもしれません。
http-serverを実行してブラウザで確認すると下記のようになりました。
(x, y, id) = (sqrt(3)/2, ½, 4423) のオブジェクトが表示されていることがわかります。
6. まとめ
この記事ではwasm側で作成したインスタンスの情報をJSに渡す方法を紹介しました。
もし複雑なデータを一気にやり取りしたい場合には役に立つはずです。
しかし、複数の型のフィールドを持つクラスのインスタンスをやり取りするのは、JS側の実装コストが高く感じました。
C++のクラス定義を変更するたびに、JSのコードにも変更が必要になるのもいただけません。
できるだけ単純な構造でデータをやりとりしたり、複数の型を使うにしてもfloatとintなど同じバイト数のものを使うと良いかもしれません。