Как я могу вернуть строку 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++, которая возвращает строку, и вы увидите вывод. В этом случае проверьте эту библиотеку.