Как я могу вернуть строку JavaScript из функции WebAssembly

Как я могу вернуть строку JavaScript из функции WebAssembly?

Может ли следующий модуль быть написан на C(++)?

export function foo() {
  return 'Hello World!';
}

Также: Могу ли я передать это движку JS для сбора мусора?

2 ответа

Решение

WebAssembly изначально не поддерживает строковый тип, скорее поддерживает i32 / i64 / f32 / f64 типы значений, а также i8 / i16 для хранения.

Вы можете взаимодействовать с экземпляром WebAssembly, используя:

  • exports где из JavaScript вы вызываете в WebAssembly, а WebAssembly возвращает один тип значения.
  • imports где WebAssembly вызывает в JavaScript с таким количеством типов значений, которое вы хотите (примечание: количество должно быть известно во время компиляции модуля, это не массив и не вариация).
  • Memory.buffer, который является ArrayBuffer которые могут быть проиндексированы с помощью (среди прочих) Uint8Array,

Это зависит от того, что вы хотите сделать, но кажется, что непосредственно получить доступ к буферу проще всего:

const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);

Если ваш модуль имел start функция затем он был выполнен во время создания экземпляра. В противном случае у вас, скорее всего, будет экспорт, который вы называете, например, instance.exports.doIt(),

Как только это будет сделано, вам нужно получить размер строки + индекс в памяти, который вы также выставите через экспорт:

const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();

Вы тогда прочитали бы это из буфера:

let s = "";
for (let i = index; i < index + size; ++i)
  s += String.fromCharCode(buffer[i]);

Обратите внимание, что я читаю 8-битные значения из буфера, поэтому я предполагаю, что строки были ASCII. Это то что std::string дал бы вам (индекс в памяти будет то, что .c_str() возвращается), но для предоставления чего-то другого, такого как UTF-8, вам нужно использовать библиотеку C++, поддерживающую UTF-8, а затем самостоятельно прочитать UTF-8 из JavaScript, получить кодовые точки и использовать String.fromCodePoint,

Вы также можете положиться на строку, заканчивающуюся нулем, чего я здесь не делал.

Вы также можете использовать TextDecoder API, как только он станет доступным в браузерах, создав ArrayBufferView в WebAssembly.Memory "s buffer (который является ArrayBuffer).


Если вместо этого вы делаете что-то вроде входа из WebAssembly в JavaScript, вы можете открыть Memory как указано выше, а затем из WebAssembly объявить импорт, который вызывает JavaScript с размером + позиция. Вы можете создать свой модуль как:

const memory = new WebAssembly.Memory({ initial: 2 });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
const instance = new WebAssembly.Instance(module, {
    imports: {
        memory: memory,
        logString: (size, index) => {
            let s = "";
            for (let i = index; i < index + size; ++i)
                s += String.fromCharCode(buffer[i]);
            console.log(s);
    }
});

Это предостережение о том, что если вы когда-нибудь увеличите объем памяти (либо с помощью JavaScript, используя Memory.prototype.grow или используя grow_memory код операции) тогда ArrayBuffer кастрируется, и вам нужно создать его заново.


На сборку мусора: WebAssembly.Module / WebAssembly.Instance / WebAssembly.Memory все это мусор, собранный движком JavaScript, но это довольно большой молоток. Вы, вероятно, хотите GC-строки, и в настоящее время это невозможно для объектов, которые живут внутри WebAssembly.Memory, Мы обсудили добавление поддержки GC в будущем.

Обновление 2020

Ситуация изменилась с тех пор, как были опубликованы другие ответы.

Сегодня я бы сделал ставку на типы интерфейсов WebAssembly - см. Ниже.

Поскольку вы спросили конкретно о C++, см.:

nbind - волшебные заголовки, которые делают вашу библиотеку C++ доступной из JavaScript.

nbind - это набор заголовков, которые делают вашу библиотеку C++11 доступной из JavaScript. С помощью одного оператора #include ваш компилятор C++ генерирует необходимые привязки без каких-либо дополнительных инструментов. Затем вашу библиотеку можно использовать как надстройку Node.js или, если она скомпилирована в asm.js с помощью Emscripten, прямо на веб-страницах без каких-либо подключаемых модулей.

Embind используется для привязки функций и классов C++ к JavaScript, чтобы скомпилированный код можно было естественным образом использовать с помощью "обычного" JavaScript. Embind также поддерживает вызов классов JavaScript из C++.

См. Следующие предложения WebAssembly:

Предложение добавляет к WebAssembly новый набор типов интерфейсов, которые описывают высокоуровневые значения (например, строки, последовательности, записи и варианты) без привязки к единому представлению памяти или схеме совместного использования. Типы интерфейсов могут использоваться только в интерфейсах модулей и могут создаваться или использоваться только декларативными интерфейсными адаптерами.

Для получения дополнительной информации и отличного объяснения см.:

Вы уже можете использовать его с некоторыми экспериментальными функциями, см.

Хороший пример из реальной жизни с использованием еще одного подхода см.:

libsodium.js - криптографическая библиотека натрия, скомпилированная для WebAssembly и чистого JavaScript с использованием Emscripten, с автоматически генерируемыми оболочками для упрощения использования в веб-приложениях.

Смотрите также:

Wasmer - это среда выполнения с открытым исходным кодом для выполнения WebAssembly на сервере. Наша миссия - сделать все программное обеспечение общедоступным. Мы поддерживаем автономный запуск модулей Wasm в нашей среде выполнения, но также можем встраивать их в несколько языков с помощью нашей языковой интеграции.

и, в частности, Wasmer-JS:

Wasmer-JS позволяет использовать скомпилированные на стороне сервера модули WebAssembly в Node.js и браузере. Проект настроен как монорепозиторий из нескольких пакетов JavaScript.

В этой статье на Hacker News также есть полезная информация.

Данный:

  • mem, то WebAssembly.Memory объект (из модуля экспорта)
  • p, адрес первого символа строки
  • len, длина строки (в байтах),

вы можете прочитать строку, используя:

let str = (new TextDecoder()).decode(new Uint8Array(mem.buffer, p, len));

Предполагается, что строка закодирована в UTF-8.

Я нашел способ взлома, аналогичный тому, что мы делаем с помощью приложения hybird, и это очень легко.

Просто введите window.alert функция, а затем верните ее:

let originAlert = window.alert;
window.alert = function(message) {
    renderChart(JSON.parse(message))
};
get_data_from_alert();
window.alert = originAlert;

и родная сторона, просто:

// Import the `window.alert` function from the Web.
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

...

pub fn get_data_from_alert() {
    alert(CHART_DATA);
}

вы можете увидеть в примерах мой GitHub: https://github.com/phodal/rust-wasm-d3js-sample

Есть более простой способ сделать это. Во-первых, вам нужен экземпляр вашего двоичного файла:

const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 });
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });

Тогда, если вы запустите console.log(instance)Почти в верхней части этого объекта вы увидите функцию AsciiToString, Передайте свою функцию из C++, которая возвращает строку, и вы увидите вывод. В этом случае проверьте эту библиотеку.

Другие вопросы по тегам