5さいでもわかる(*╹▽╹*) WebAssemblyを使ってJavaScriptからC++を動かしてみよう!


みなさんこんにちは、こんばんは!!!!
今回はWebAssemblyを使ってJavaScriptからC++を動かしてみます

なおこの記事を書いた人はただの高校生です 最近はライブラリに頼らず自力で図形描画してたりします 初投稿記事です

認識が間違ってるよ、とかあればコメント欄で連絡してくれるとありがたいです。助かります。

そもそもWebAssemblyって?

簡単に要約すると「高水準の言語をバイナリー形式に変換してウェブ上から動かすことができる」というのです

JavaScriptに実行速度が求められる時代になりましたが、インタプリンタ型で動的型付けをしている以上、コンパイラ言語より遅くなってしまう問題があります (グラフィック使ったりめちゃくちゃ凝ったサイトとかあるもんね)

そこでWebAssembly、バイナリに変換することで実際に読み込む量を減少させることができ、またC++など処理が速い言語を利用することができます

最高ですね

使ってみよう インストール

今回はwasmを作ってもらうためにemscriptenを使うことにします
インストールについての解説サイト
ここは指示に従えばすんなりと行けるはずです 任意の場所でgit cloneしてください

補足 emccはemsdk/upstream/emscripten/emccにあります

使ってみよう コードを書く(C++)

基本

forjs.cpp
//ファイル名は任意です

#include <stdio.h>
#include <vector>
#include <string>
#include <iostream>
#include <emscripten.h>
#include <emscripten/bind.h>
using namespace emscripten; //名前空間、これは任意です 私はstdはusing namespaceしない派です

//ここにクラスとかを書く 
class ClassName //任意の名前
{
private:

public:
    ClassName() //コンストラクタ
    {
    }
    void FunctionName() //これも任意の名前
    {
        std::cout << "おうどんたべたい" << std::endl;
    }
};

EMSCRIPTEN_BINDINGS(forjs){
    register_vector<std::string>("vector<std::string>");
    register_vector<int>("vector<int>");

    class_<ClassName>("ClassName")
        .constructor<>()
        .function("FunctionName", &ClassName::FunctionName);
}

最低これで動きます

EMSCRIPTEN_BINDINGS

簡単に要約すると「JavaScriptとC++の関係を記述する」というものです

forjs.cpp
    ClassName(int value) //コンストラクタ
    {
    }

またこのような感じでコンストラクタに引数を設定する場合は下記のように記述する必要があります

forjs.cpp
    class_<ClassName>("ClassName")
        .constructor<int>()

boost pythonもほぼ同じ書き方 姉妹かな?

bits/stdc++.h は使えないようです 各自それぞれ標準ライブラリをincludeしてください bits/stdc++.hはよく使われる標準ライブラリを一括でincludeしてくれるものなので、別になくても困りません

std::vectorを使えるようにする

何もしないと JavaScriptとC++の間の引数にstd::vectorを設定することができません
そのため EMSCRIPTEN_BINDINGS 内でvectorが使えるように設定する必要があります
vector内に入れる型ごとに書かないといけないようです、あーめんどくさい

forjs.cpp
    register_vector<std::string>("vector<std::string>");
    register_vector<int>("vector<int>");

どこまで記述すればいいのか?

EMSCRIPTEN_BINDINGSの中にどこまでclass周りの定義をしないといけないのか、
それは「JavaScriptに渡すクラスは基本書く」です
JavaScript側でインスタンス化するときも、C++内でインスタンス化して渡すときも、です
std::vectorの件もこの考え方に基づいてなんですかね?

使ってみよう ビルドする!

makefileとかがおすすめです(オプションの数も多くなりがちなため)

emccコンパイラに対してg++を使う時と同じように ビルド対象、出力方法・・・などを記述していきます
また--bind -s WASM=1 を引っ付けます --bind は C++側のクラスをJavaScript側で呼び出すのに必要なようです

-o 任意の名前.js にします これによってwasmとwasmを簡単に利用できるようにするラッパー(js)が出力されます

利用できるメモリ量が決まっているようです
利用できるメモリ量を増やすには -s INITIAL_MEMORY=任意の値 を追加します
この数字は 64*1024*8 = 524288 = 64KBの倍数じゃないとだめなようです

emcc --bind forjs.cpp -s INITIAL_MEMORY=任意の値 -s WASM=1 -o output.js -std=c++14

output.jsのとこは任意の名前でも大丈夫です

拡張子をwasmにするとwasmが単体で出力され htmlにするとhtml + js + wasmが出力されます この出力されたhtmlを開くとWebAssemblyに関する記事あるあるのサイトが出てきます

使ってみよう コードを書く(JavaScript)

main.js
var Module = {
    onRuntimeInitialized: function () {
        ///この中にモジュールが読み込まれた時の処理を書く!!!

        var hoge= new Module.ClassName();
        hoge.FunctionName();
        ///こんな感じでインスタンス化できます いいね
    }
};

使ってみよう コードを書く(html)

もうすぐ終わり、お疲れさまでした

main.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>うどんたべたい!</title>
</head>

<body>
    <script src="main.js"></script>
    <script src="output.js"></script>
</body>

</html>

動かす前に!

セキュリティの観点からなのか、httpサーバを立てる必要があるらしいです

server.py
import http.server
import socketserver
import sys
Handler = http.server.SimpleHTTPRequestHandler

port_number = int(input())

Handler.extensions_map['.wasm'] = 'application/wasm'
with socketserver.TCPServer(("", port_number), Handler) as httpd:
    httpd.serve_forever()

server.pyの記述についてはこの記事を参考にさせていただきました

python3 server.pyしたあとに数字を打ち込んだらサーバが起動します
htmlと同じ階層に置いたほうがいいかも

動かす!

Google chromeなどWebAssembly対応ブラウザから localhost:さっき打った番号 にアクセスしましょう
そこでmain.htmlを選択し、コンソール画面を開くと文字列が出力されているはずです

お疲れ様でした
今回のソースコード
今作っっている図形描画プログラム

参考文献

WebAssemblyの概要
emscripten
WebAssemblyをhtmlから呼び出す
WebAssemblyでC++とJavaScript間のやり取り
emscripten の bind で JavaScript オブジェクトを返す方法