Emscripten の embind

JS を Wasm にバインドします。

前回の Wasm の記事で、 C ライブラリを Wasm にコンパイルしてウェブで使用できるようにする方法を解説しています。1 つのこと 私(そして多くの読者)にとって最も印象的だったのは、下品でやや気まずく、 使用する Wasm モジュールの関数を手動で宣言する必要があります。 念のため、これから説明するコード スニペットを以下に示します。

const api = {
    version: Module.cwrap('version', 'number', []),
    create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
    destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};

ここでは、先ほどマークした関数の名前を宣言します。 EMSCRIPTEN_KEEPALIVE、その戻り値の型と型 確認します。その後、api オブジェクトのメソッドを使用して、 使用できます。ただし、この方法で Wasm を使用すると、 メモリのチャンクを手動で移動する必要があり、多くのライブラリが 使用するのが面倒な API です。もっと良い方法はないでしょうか?なぜそう思うのか、そうでなければ 記事の内容は何でしょうか

C++ 名のマングリング

しかし、開発者エクスペリエンスの面では、 ただし、より差し迫った理由があります。それは、C をコンパイルするときに C++ コードの場合、各ファイルは個別にコンパイルされます。次にリンカーが いわゆるオブジェクトファイルを すべて統合して 表示されます。C では、関数の名前はオブジェクト ファイルで引き続き使用できる 指定することもできます。C 関数を呼び出すために必要なのは名前だけです。 これは cwrap() に文字列として提供しています。

一方、C++ は関数のオーバーロードをサポートしているため、 シグネチャが異なる(例: 型の異なるパラメータなど)を使用します。コンパイラ レベルでは、add のような適切な名前 関数内のシグネチャをエンコードするものにマングリングされます。 指定します。その結果、この関数をルックアップして、 名前で終了します。

エンバインドを入力

embind Emscripten ツールチェーンの一部であり、多数の C++ マクロを提供します これらを使用すると、C++ コードにアノテーションを付けることができます。どの関数、列挙型、 クラスまたは値の型を指定します。では始めましょう 単純な関数を使って簡単に実行できます。

#include <emscripten/bind.h>

using namespace emscripten;

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

std::string exclaim(std::string message) {
    return message + "!";
}

EMSCRIPTEN_BINDINGS(my_module) {
    function("add", &add);
    function("exclaim", &exclaim);
}

以前の記事と比べると、emscripten.h は除外されています。その理由は、 関数に EMSCRIPTEN_KEEPALIVE アノテーションを付ける必要がなくなりました。 代わりに EMSCRIPTEN_BINDINGS セクションがあり、その下に名前をリストしています。 これを使って関数を JavaScript に公開します。

このファイルをコンパイルするには、同じ設定(または、必要に応じて同じ設定)を使用できます。 Docker イメージなど)を前のバージョンと同様に 記事をご覧ください。エンバインドを使用するには --bind フラグを追加します。

$ emcc --bind -O3 add.cpp

あとは、新たに作成したサーバーを読み込む HTML ファイルを作成するだけです。 作成された Wasm モジュール:

<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
    console.log(Module.add(1, 2.3));
    console.log(Module.exclaim("hello world"));
};
</script>

ご覧のとおり、cwrap() は使用されなくなりました。これは問題なく機能します 説明します。しかも、手動でコピーする手間が省けます。 文字列を機能させることができます。embind では 無料で入手できます 型チェックあり:

誤った数の引数で関数を呼び出すと DevTools でエラーが発生する
引数が正しくないか、
種類

エラーを処理する代わりに早期に発見できるため、非常に便利です。 ときどき扱いにくい Wasm エラーです。

オブジェクト

JavaScript のコンストラクタと関数の多くは、オプション オブジェクトを使用します。すてきな JavaScript では作成されませんが、Wasm で手動で実現するのはかなり面倒な作業です。エンバインド ここでも役に立つでしょう。

たとえば、非常に便利な C++ 関数を思いつきました。 ウェブでもすぐに使いたいと思っています。手順は次のとおりです。

#include <emscripten/bind.h>
#include <algorithm>

using namespace emscripten;

struct ProcessMessageOpts {
    bool reverse;
    bool exclaim;
    int repeat;
};

std::string processMessage(std::string message, ProcessMessageOpts opts) {
    std::string copy = std::string(message);
    if(opts.reverse) {
    std::reverse(copy.begin(), copy.end());
    }
    if(opts.exclaim) {
    copy += "!";
    }
    std::string acc = std::string("");
    for(int i = 0; i < opts.repeat; i++) {
    acc += copy;
    }
    return acc;
}

EMSCRIPTEN_BINDINGS(my_module) {
    value_object<ProcessMessageOpts>("ProcessMessageOpts")
    .field("reverse", &ProcessMessageOpts::reverse)
    .field("exclaim", &ProcessMessageOpts::exclaim)
    .field("repeat", &ProcessMessageOpts::repeat);

    function("processMessage", &processMessage);
}

processMessage() 関数のオプションの構造体を定義しています。 EMSCRIPTEN_BINDINGS ブロック。value_object を使用して、JavaScript に オブジェクトとしてこの C++ 値を使用します。value_array を使用することもできます。 この C++ 値を配列として使用します。また、processMessage() 関数もバインドします。 残りは魔法です。これで、processMessage() 関数を ボイラープレート コードのない JavaScript:

console.log(Module.processMessage(
    "hello world",
    {
    reverse: false,
    exclaim: true,
    repeat: 3
    }
)); // Prints "hello world!hello world!hello world!"

クラス

説明の完全性を期すために、embind を使用して BigQuery で クラス全体を拡張できるため、ES6 クラスとの多くの相乗効果が生まれます。おそらく、 パターンが見えてくるかもしれません。

#include <emscripten/bind.h>
#include <algorithm>

using namespace emscripten;

class Counter {
public:
    int counter;

    Counter(int init) :
    counter(init) {
    }

    void increase() {
    counter++;
    }

    int squareCounter() {
    return counter * counter;
    }
};

EMSCRIPTEN_BINDINGS(my_module) {
    class_<Counter>("Counter")
    .constructor<int>()
    .function("increase", &Counter::increase)
    .function("squareCounter", &Counter::squareCounter)
    .property("counter", &Counter::counter);
}

JavaScript 側では、ネイティブ クラスのように見えます。

<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
    const c = new Module.Counter(22);
    console.log(c.counter); // prints 22
    c.increase();
    console.log(c.counter); // prints 23
    console.log(c.squareCounter()); // prints 529
};
</script>

C についてはどうでしょうか。

embind は C++ 用に記述されており、C++ ファイルでのみ使用できます。 C ファイルに対してリンクできないことを意味します。C と C++ を混在させる場合、 入力ファイルを 2 つのグループに分けます。1 つは C 用、もう 1 つは C++ ファイル用です。 emcc の CLI フラグを次のように拡張します。

$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp

まとめ

embind により、作業する際のデベロッパー エクスペリエンスが大幅に向上します。 使用しています。この記事では、エンバインド オファーのすべてのオプションについて説明しているわけではありません。 関心をお持ちの場合は、embind の ドキュメントをご覧ください。 embind を使用すると、Wasm モジュールと JavaScript グルーコードは、gzip 圧縮すると最大 11,000 大きくなります(特に小さい 説明します。Wasm サーフェスが非常に小さい場合は、embind が 価値があるのですそれでも ぜひお試しください。