👻

React+TypeScriptでWebAssembly010。Wasm。js->C++の関数呼び出しのまとめ

2024/01/29に公開

<- React+TypeScriptでWebAssembly009
                    React+TypeScriptでWebAssembly011 ->

Abstract

WebAssemblyで、jsからC++の関数コールをまとめてみた。
React+TypeScriptでWebAssembly005。←の回で、WebAssemblyビルドして動かすことはできたけど、C++コード無駄があるな~と思ってた。
具体的には下記コードの67,68,70行目。
67行目でC++側のメモリ確保
     ↓
68行目でその領域にコピー
     ↓
70行目でその領域を解放
まぁ、無駄よね。66行目でgetImageData()を取得してんだから、そのままC++に渡せばいい。
というモチベーションで、js->C++関数コールを調べてみた。

App.tsx(4-10行目)
  58:    /* ループ */
  59:    const loop = () => {
  60:      const ctx = ctxRef.current;
  61:      const video = videoRef.current;
  62:      const canvas = canvasRef.current;
  63:      const wasm = refWasm.current;
  64:      if(ctx && video && canvas && wasm) {
  65:        ctx.drawImage(video, 0, 0);
             //  ↑videoから画像を取得
+ 66:        const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
             //  ↑取得したデータを取り出す
+ 67:        const buffer = wasm._creata_buffer(data.data.length);
             //  ↑自作関数を呼び出し
+ 68:        wasm.HEAPU8.set(data.data, buffer);
  69:        wasm._Convert(buffer, data.width, data.height, cnt);
             //  ↑自作関数を呼び出し
+ 70:        wasm._destroy_buffer(buffer);
             //  ↑自作関数を呼び出し
  71:        cnt++;
  72:        if(cnt>=200) cnt = 0;
  73:      }
  74:      loopRef.current = requestAnimationFrame(loop);
  75:    }

結論

今回の成果物はココ↓
https://github.com/aaaa1597/ReactTs-WebAsm010

参考 : https://perso-laris.univ-angers.fr/~cottenceau/tutowasmcpp.pdf

前提

準備

ubuntuだよ。

1.テンプレートのソースコード一式を取得

github取得
$ cd ~
$ git clone https://github.com/aaaa1597/reacttscppwasm_tmplate.git
$ mv reacttscppwasm_tmplate ReactTs-WebAsm010
$ cd ReactTs-WebAsm010/

2.ソースコードをWasmビルド

srcをビルド
$ cd ~/ReactTs-WebAsm010/wasm
$ mkdir build && cd build
$ emcmake cmake ..
$ emmake make
$ mv cppmain.js ..
$ mv cppmain.wasm ..

3.Webカメラの設定(内蔵カメラならこの手順は不要)

4.サーバ起動

サーバ起動
$ cd ~/ReactTs-WebAsm010/wasm
$ python3 -m http.server 8080

5.サーバにアクセス。

ブラウザから http://localhost:8080/ にアクセスする。

準備完了!!


実装

1.CMakeLists.txtの修正

CMakeLists.txt
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s USE_ZLIB=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 --bind -O3 -s LLD_REPORT_UNDEFINED -s DEMANGLE_SUPPORT=1")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s USE_ZLIB=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 -lembind -O3 -s LLD_REPORT_UNDEFINED -s DEMANGLE_SUPPORT=1")

--bind を -lembind に変更。

2. wasm/ifwasm.cppの修正

カメラ読込みとかは削除。最小構成に。

wasm/ifwasm.cpp
#include <string>
#include <vector>
#include <complex>
#include <emscripten.h>
#include <emscripten/bind.h>

#define LOG_OUTPUT 1
#if LOG_OUTPUT
EM_JS(int, console_log, (const char *logstr), {
  console.log('aaaaa ' + UTF8ToString(logstr));
  return 0;
});
#else
#define console_log(logstr)
#endif

extern "C" {
// int,int,int
int func1(int a, int b){
	console_log(__PRETTY_FUNCTION__);
	return a + b;
}

// double, double, double
double func2(double a, double b){
	console_log(__PRETTY_FUNCTION__);
	return a + b;
}

// std::string, std::string, std::string
std::string func3(const std::string & s1, const std::string & s2){
	console_log(__PRETTY_FUNCTION__);
	return s1 + " " + s2;
}

// int配列, int配列, int配列
emscripten::val func4(const emscripten::val arg1, const emscripten::val arg2){
	console_log(__PRETTY_FUNCTION__);
	std::vector<int> v1 = vecFromJSArray<int>(arg1);
	std::vector<int> v2 = vecFromJSArray<int>(arg2);
	std::vector<int> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(),std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}

// UInt8配列, UInt8配列, UInt8配列
emscripten::val func5(const emscripten::val arg1, const emscripten::val arg2) {
	console_log(__PRETTY_FUNCTION__);
	std::vector<uint8_t> v1 = vecFromJSArray<uint8_t>(arg1);
	std::vector<uint8_t> v2 = vecFromJSArray<uint8_t>(arg2);
	std::vector<uint8_t> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(),std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}

// string配列, string配列, string配列
emscripten::val func6(const emscripten::val arg1, const emscripten::val arg2){
	console_log(__PRETTY_FUNCTION__);
	std::vector<std::string> v1 = vecFromJSArray<std::string>(arg1);
	std::vector<std::string> v2 = vecFromJSArray<std::string>(arg2);
	std::vector<std::string> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(), std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}


// JSオブジェクト, JSオブジェクト, JSオブジェクト
emscripten::val func7(const emscripten::val arg1, const emscripten::val arg2){
	console_log(__PRETTY_FUNCTION__);
	int x = arg1["x"].as<int>();
	int y = arg1["y"].as<int>();
	int w = arg2["x"].as<int>();
	int h = arg2["y"].as<int>();
	emscripten::val ret = emscripten::val::global("rect").new_(x,y,w,h);
	return ret;
}

// JSONオブジェクト, JSONオブジェクト, JSONオブジェクト
emscripten::val func8(const emscripten::val arg1, const emscripten::val arg2) {
	console_log(__PRETTY_FUNCTION__);
	int x = arg1["x2"].as<int>();
	int y = arg1["y2"].as<int>();
	int w = arg2["w2"].as<int>();
	int h = arg2["h2"].as<int>();
	emscripten::val ret = emscripten::val::object();
	ret.set("xx",x);
	ret.set("yy",y);
	ret.set("ww",y);
	ret.set("hh",y);
	return ret;
}

EMSCRIPTEN_BINDINGS(module) {
	emscripten::function("f1", &func1);
	emscripten::function("f2", &func2);
	emscripten::function("f3", &func3);
	emscripten::function("f4", &func4);
	emscripten::function("f5", &func5);
	emscripten::function("f6", &func6);
	emscripten::function("f7", &func7);
	emscripten::function("f8", &func8); 
}
}	//extern "C" 

EMSCRIPTEN_BINDINGSで関数名の定義をしているのがポイント。
これで、Module.xxxx()の形でJavaScriptから、C++を呼び出せるようになる。

3. wasm/index.htmlの修正

最小構成に。

wasm/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

<body>
	<style type="text/css">
		 textarea {
		 width: 100%;
		 resize: vertical;
		}
	</style>
	<div >
		<button onclick="bclick()">Click</button>
		<textarea id="textin" rows="5"></textarea>
		<textarea id="textout" rows="5"></textarea>
	</div>

	<!-- glue-code generated by emscripten -->
	<script src="cppmain.js"></script>
	<script>
	<!-- called just after the WASM file is loaded -->
	Module['onRuntimeInitialized'] = function(){
		textin.value = 'wasm OK';
	}
	function point(x,y)
	{
		this.x=x;
		this.y=y;
		this.toString=function(){
			return '('+this.x+','+this.y+')';
		}
	}
	function rect(x,y,w,h)
	{
		this.x=x;
		this.y=y;
		this.w=w;
		this.h=h;
		this.toString=function(){
			return '('+this.x+','+this.y+','+this.w+','+this.h+')';
		}
	}

	<!-- click handler -->
	function bclick(){
		textout.value ="f1(1,2)=" + Module.f1(1,2) + "\n";
		textout.value+="f2(2.5,3.5)=" + Module.f2(2.5, 3.5) + "\n";
		textout.value+="f3('aaa', 'bbbb')=" + Module.f3('aaa', 'bbbb') + "\n";
		var arg41=[1,2,3,4], arg42=[5,6,7,8];
		textout.value+="f4([1,2,3,4], [5,6,7,8])=" + Module.f4(arg41, arg42) + "\n";
		var arg51=new Uint8Array([1,2,3,4]), arg52=new Uint8Array([5,6,7,8]);
		textout.value+="f5(new Uint8Array([1,2,3,4]), new Uint8Array([5,6,7,8]))=" + Module.f5(arg51, arg52) + "\n";
		var arg61=['aa','bb','cc','dd'], arg62=['ee','ff','gg','hh'];
		textout.value+="f6(['aa','bb','cc','dd'], ['ee','ff','gg','hh'])=" + Module.f6(arg61, arg62) + "\n";
		var arg71=new point(1,2), arg72=new point(3,4);
		textout.value+="f7(new point(1,2), new point(3,4))=" + Module.f7(arg71, arg72) + "\n";
		textout.value+="f8({x2:1, y2:2},{w2:3, h2:4})=" + JSON.stringify(Module.f8({x2:1, y2:2},{w2:3, h2:4}));
	}
	</script>
</body>
</html>

4.Wasmビルド

wasmをビルド
$ cd ~/ReactTs-WebAsm010/wasm
$ rm -rf build
$ mkdir build && cd build
$ emcmake cmake ..
$ emmake make
$ mv cppmain.js ..
$ mv cppmain.wasm ..

5.サーバ起動

サーバ起動
$ cd ~/ReactTs-WebAsm010/wasm
$ python3 -m http.server 8080

6.サーバにアクセス。

ブラウザから http://localhost:8080/ にアクセスする。


出来た!!

結果のまとめ

1. int,int,int

// 実装(C++)
int func1(int a, int b){
	console_log(__PRETTY_FUNCTION__);
	return a + b;
}
// 呼出し(JavaScript)
console.log("f1(1,2)=" + Module.f1(1,2));

出力: f1(1,2)=3

2. double,double,double

// 実装(C++)
double func2(double a, double b){
	console_log(__PRETTY_FUNCTION__);
	return a + b;
}
// 呼出し(JavaScript)
console.log("f2(2.5,3.5)=" + Module.f2(2.5, 3.5));

出力: f2(2.5,3.5)=6

3. std::string, std::string, std::string

// 実装(C++)
std::string func3(const std::string &s1, const std::string &s2){
	console_log(__PRETTY_FUNCTION__);
	return s1 + " " + s2;
}
// 呼出し(JavaScript)
console.log("f3('aaa', 'bbbb')=" + Module.f3('aaa', 'bbbb'));

出力: f3('aaa', 'bbbb')=aaa bbbb

4. int配列, int配列, int配列

// 実装(C++)
emscripten::val func4(const emscripten::val &arg1, const emscripten::val &arg2){
	console_log(__PRETTY_FUNCTION__);
	std::vector<int> v1 = vecFromJSArray<int>(arg1);
	std::vector<int> v2 = vecFromJSArray<int>(arg2);
	std::vector<int> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(),std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}
// 呼出し(JavaScript)
var arg41=[1,2,3,4], arg42=[5,6,7,8];
console.log("f4([1,2,3,4], [5,6,7,8])=" + Module.f4(arg41, arg42));

出力: f4([1,2,3,4], [5,6,7,8])=1,2,3,4,5,6,7,8
配列は、emscripten::valを使えば解決するらしい。emscripten::val便利!!

5. UInt8配列, UInt8配列, UInt8配列

emscripten::val func5(const emscripten::val &arg1, const emscripten::val &arg2) {
	console_log(__PRETTY_FUNCTION__);
	std::vector<uint8_t> v1 = vecFromJSArray<uint8_t>(arg1);
	std::vector<uint8_t> v2 = vecFromJSArray<uint8_t>(arg2);
	std::vector<uint8_t> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(),std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}
// 呼出し(JavaScript)
var arg51=new Uint8Array([1,2,3,4]), arg52=new Uint8Array([5,6,7,8]);
console.log("f5(new Uint8Array([1,2,3,4]), new Uint8Array([5,6,7,8]))=" + Module.f5(arg51, arg52));

出力: f5(new Uint8Array([1,2,3,4]), new Uint8Array([5,6,7,8]))=1,2,3,4,5,6,7,8

6. string配列, string配列, string配列

emscripten::val func6(const emscripten::val &arg1, const emscripten::val &arg2){
	console_log(__PRETTY_FUNCTION__);
	std::vector<std::string> v1 = vecFromJSArray<std::string>(arg1);
	std::vector<std::string> v2 = vecFromJSArray<std::string>(arg2);
	std::vector<std::string> retvec(v1.begin(), v1.end());
	std::copy(v2.begin(),v2.end(), std::back_inserter(retvec));
	emscripten::val retarray = emscripten::val::array(retvec.begin(), retvec.end());
	return retarray;
}
// 呼出し(JavaScript)
var arg61=['aa','bb','cc','dd'], arg62=['ee','ff','gg','hh'];
console.log("f6(['aa','bb','cc','dd'], ['ee','ff','gg','hh'])=" + Module.f6(arg61, arg62));

出力: f6(['aa','bb','cc','dd'], ['ee','ff','gg','hh'])=aa,bb,cc,dd,ee,ff,gg,hh

7. JSオブジェクト, JSオブジェクト, JSオブジェクト

emscripten::val func7(const emscripten::val &arg1, const emscripten::val &arg2){
	console_log(__PRETTY_FUNCTION__);
	int x = arg1["x"].as<int>();
	int y = arg1["y"].as<int>();
	int w = arg2["x"].as<int>();
	int h = arg2["y"].as<int>();
	emscripten::val ret = emscripten::val::global("rect").new_(x,y,w,h);
	return ret;
}
// 呼出し(JavaScript)
function point(x,y) {
	this.x=x;
	this.y=y;
	this.toString=function(){
		return '('+this.x+','+this.y+')';
	}
}

function rect(x,y,w,h) {
	this.x=x;
	this.y=y;
	this.w=w;
	this.h=h;
	this.toString=function(){
		return '('+this.x+','+this.y+','+this.w+','+this.h+')';
	}
}
var arg71=new point(1,2), arg72=new point(3,4);
console.log("f7(new point(1,2), new point(3,4))=" + Module.f7(arg71, arg72));

出力: f7(new point(1,2), new point(3,4))=(1,2,3,4)

8. JSONオブジェクト, JSONオブジェクト, JSONオブジェクト

emscripten::val func8(const emscripten::val &arg1, const emscripten::val &arg2) {
	console_log(__PRETTY_FUNCTION__);
	int x = arg1["x2"].as<int>();
	int y = arg1["y2"].as<int>();
	int w = arg2["w2"].as<int>();
	int h = arg2["h2"].as<int>();
	emscripten::val ret = emscripten::val::object();
	ret.set("xx",x);
	ret.set("yy",y);
	ret.set("ww",y);
	ret.set("hh",y);
	return ret;
}
// 呼出し(JavaScript)
console.log("f8({x2:1, y2:2},{w2:3, h2:4})=" + JSON.stringify(Module.f8({x2:1, y2:2},{w2:3, h2:4})));

出力: f8({x2:1, y2:2},{w2:3, h2:4})={"xx":1,"yy":2,"ww":2,"hh":2}

まとめ。
emscripten::valの使い方が分かれば、C++,WebAssemblyはどうにかなりそう。


<- React+TypeScriptでWebAssembly009
                    React+TypeScriptでWebAssembly011 ->

Discussion