React+TypeScriptでWebAssembly010。Wasm。js->C++の関数呼び出しのまとめ
<- 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++関数コールを調べてみた。
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://perso-laris.univ-angers.fr/~cottenceau/tutowasmcpp.pdf
前提
- WebAssemblyのビルド環境、構築済み。[環境構築]Ubuntu22.04で、WebAssemblyのOpenCV,C++,CMakeの開発環境を構築してみた。
- React+Typescriptの開発環境は構築済 [環境構築]WindowsにVSCode+React+TypeScriptの開発環境を構築してみた。
- ベースはこれ→reacttscppwasm_tmplate。
準備
ubuntuだよ。
1.テンプレートのソースコード一式を取得
$ cd ~
$ git clone https://github.com/aaaa1597/reacttscppwasm_tmplate.git
$ mv reacttscppwasm_tmplate ReactTs-WebAsm010
$ cd ReactTs-WebAsm010/
2.ソースコードをWasmビルド
$ cd ~/ReactTs-WebAsm010/wasm
$ mkdir build && cd build
$ emcmake cmake ..
$ emmake make
$ mv cppmain.js ..
$ mv cppmain.wasm ..
3.Webカメラの設定(内蔵カメラならこの手順は不要)
- Webカメラ挿して。
- Windowsの設定 Windowsで設定
- Virtual Box設定 Virtual Box->デバイス->Webカメラ->HD Web Cameraにチェック
4.サーバ起動
$ cd ~/ReactTs-WebAsm010/wasm
$ python3 -m http.server 8080
5.サーバにアクセス。
ブラウザから http://localhost:8080/ にアクセスする。
準備完了!!
実装
1.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の修正
カメラ読込みとかは削除。最小構成に。
#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の修正
最小構成に。
<!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ビルド
$ 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