Он привязывает 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++ каждый файл компилируется отдельно. Затем линкер объединяет все эти так называемые объектные файлы и преобразует их в wasm-файл. В C имена функций по-прежнему доступны линкеру в объектном файле. Всё, что нужно для вызова функции C, — это имя, которое мы передаем в виде строки в cwrap() .
 С другой стороны, C++ поддерживает перегрузку функций, то есть одну и ту же функцию можно реализовать несколько раз, если её сигнатура будет отличаться (например, параметры будут иметь разные типы). На уровне компилятора такое красивое имя, как add , будет искажено, превратившись в нечто, кодирующее сигнатуру в имени функции для компоновщика. В результате мы больше не сможем найти нашу функцию по её имени.
Введите embind
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 предоставляет вам это бесплатно, вместе с проверкой типов: 

Это очень здорово, поскольку мы можем выявлять некоторые ошибки на ранних стадиях, вместо того чтобы разбираться с порой довольно громоздкими ошибками wasm.
Объекты
Многие конструкторы и функции JavaScript используют объекты параметров. Это удобный шаблон в JavaScript, но его очень сложно реализовать вручную в wasm. embind может помочь и здесь!
Например, я придумал невероятно полезную функцию 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() , а остальное — магия embind. Теперь я могу вызывать функцию processMessage() из JavaScript без какого-либо шаблонного кода:
console.log(Module.processMessage(
    "hello world",
    {
    reverse: false,
    exclaim: true,
    repeat: 3
    }
)); // Prints "hello world!hello world!hello world!"
Классы
Для полноты картины я также должен показать вам, как embind позволяет выводить целые классы, что обеспечивает значительную синергию с классами 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>
А как насчет С?
 Функция embind написана для C++ и может использоваться только в файлах C++, но это не значит, что нельзя компоновать файлы C! Чтобы объединить C и C++, достаточно разделить входные файлы на две группы: одну для файлов C и одну для файлов C++, а также добавить флаги CLI для emcc следующим образом:
$ 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. Если вам интересно, рекомендую продолжить изучение документации Embind . Имейте в виду, что использование Embind может увеличить размер как вашего wasm-модуля, так и вашего связующего JavaScript-кода до 11 КБ при сжатии gzip — особенно заметно для небольших модулей. Если у вас очень маленькая область wasm, Embind может стоить дороже, чем он того стоит в рабочей среде! Тем не менее, вам определённо стоит попробовать.
