Emscripten 的魔法

這會將 JS 繫結至 wasm!

上一篇Wasm 文章提到了我 瞭解如何將 C 程式庫編譯為 wasm,以便在網路上使用。有一件事 我 (以及許多讀者) 都很滿意這一點 您必須手動宣告自己使用的 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++ 程式碼,每個檔案都會分開編譯。接著連結器會處理 將所有這類所謂的物件檔案傳遞在一起,然後轉換成 am。 檔案。使用 C 時,仍可在物件檔案中使用函式名稱 供連結器使用你只需呼叫 C 函式即可 我們以字串形式提供給 cwrap()

另一方面,C++ 支援函式超載,表示只要實作 在簽章不同的情況下多次相同的函式 (例如 不同的類型參數)。在編譯器層級,有一個不錯的名稱,例如 add 會「破壞」為用來編碼函式簽章的內容 連結器的名稱因此,我們無法查詢函式 命名

輸入 Ebind

embind 是 Emscripten 工具鍊的一部分,提供了多種 C++ 巨集 可讓您為 C++ 程式碼加上註解您可以宣告哪些函式、列舉 您打算從 JavaScript 使用的類別或值類型。開始 幾個簡單函式來操作

#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 映像檔),如上一個 一文。如要使用 embind, 我們會加上 --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 可讓您免費取得 具有類型檢查:

叫用引數數量錯誤的函式時,開發人員工具發生錯誤
或是引數包含錯誤資訊
類型

這項功能很實用,因為我們能及早發現部分錯誤,而不必處理 偶爾出現很不準的真的是錯的

物件

許多 JavaScript 建構函式和函式都會使用選項物件。很棒 模式,但手動操作時卻無所適從。碎形 也可以在此提供協助!

例如,我提出了這個「非常」實用的 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() 函式,且 其餘部分會繫結至「magic」。現在可以從processMessage() 不含任何樣板程式碼的 JavaScript:

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

類別

為了保持完整性,我應該也示範 整個類別,並帶來許多與 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++,您只需要 將輸入檔案分成兩個群組:一個用於 C,另一個用於 C++ 檔案。 擴充 emcc 的 CLI 標記,如下所示:

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

結論

embind 大幅改善開發人員體驗 搭配 wasm 和 C/C++本文並未涵蓋所有方案的合併選項。 有興趣的話,建議您繼續處理 embind's 說明文件。 請記住,使用 embind 可同時讓 wasm 模組和 JavaScript 的黏合程式碼在 gzip 時放大到最高 11k (尤其是在 模組。如果您只有非常小的駝峰表面,繫結成本可能會高於 值得在正式環境中使用!儘管如此 只要試用看看