Передавать данные между кодом javascript и WebAssembly несколько сложнее, чем передавать функции. Для этого необходимо создать специальный объект WebAssembly.Memory. Этот объект представляет блок байтов, к которому могут иметь доступ как код JavaScript, так и код WebAssembly. В этом блоке размещаются все данные, которые создаются в коде WebAssembly.
Для создания объекта WebAssembly.Memory можно использовать конструктор, который принимает объект с двумя полями: initial и maximum.
Свойство initial устанавливает начальный размер блока памяти. Свойство maximum устанавливает максимальный размер блока памяти. Значением этих свойств является количество страниц в блоке памяти. Одна страница равна 65536 байтам.
При использовании Emscripten автоматически создается блок памяти, где для каждого из свойств устанавливается 256 страниц. Что эквивалентно следующему блоку:
var memObj = new WebAssembly.Memory({initial: 256, maximum: 256});
После создания объекта Memory мы можем обращаться к нему через поле buffer. С помощью этого буфера мы можем обращаться к созданному блоку памяти и создать в нем массив для передачи данных. На текущий момент доступно создание только четырех типов массивов:
Uint32Array
Int32Array
Float32Array
Float64Array
После создания массива одного из этих типов можно считывать или производить запись данных в созданный блок памяти с помощью стандартных операций массива. Например, типовое создание массива:
var memObj = new WebAssembly.Memory({initial: 256, maximum: 256}); var block = new Uint32Array(memObj.buffer, offset, length);
В данном случае создается массив чисел. Первый параметр конструктора - memObj.buffer - указывает на блок памяти, где размещается массив. Параметр offset указывает на смещение относительно блока памяти. Поскольку блок памяти инкапсулирует все данные, в котором может быть множество массивов, и какой-то определенный массив может начинаться в этом блоке с определенного смещения. И третий параметр length указывает на размер массива.
Рассмотрим простейший пример. Определим в файле hello.c следующий код:
#define NUM_VALS 8 int numbers[NUM_VALS]; int sum() { int result = 0; for(int i=0; i < NUM_VALS; i++) { result += numbers[i]; } return result; } int* getOffset() { return &numbers[0]; }
Здесь определен массив numbers, который содержит 8 чисел. Функция sum вычисляет сумму элементов этого массива. Причем здесь мы не определяем содержимое массива - это будет делат код из javascript.
Кроме того, здесь определена функция getOffset, которая возвращает адрес массива - то есть указатель на первый элемент массива.
Для использования этого кода определим следующую страницу index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebAssembly Application</title> </head> <body> <div id="container"></div> <script> if ("WebAssembly" in window) { // создаем блок памяти var memObj = new WebAssembly.Memory({initial: 256, maximum: 256}); var importObject = { env: { memory: memObj} }; var num_vals = 8; // количество элементов в массиве WebAssembly.instantiateStreaming(fetch("hello.wasm"), importObject) .then(result => { // Получаем адрес массива из wasm offset = result.instance.exports._getOffset(); // создаем массив var numbers = new Uint32Array(memObj.buffer, offset, num_vals); // инициализируем массив for(let i = 0; i < num_vals; i++){ numbers[i] = i+1; } // вычисляем сумму элементов массива let sum = result.instance.exports._sum(); document.getElementById("container").innerHTML = "Sum: " + sum; }) .catch(console.error); } </script> </body> </html>
Здесь вначале создается блок памяти из 256 страниц. Свойство memory объекта importObject.env указывает на этот блок памяти. Далее объект importObject передается в WebAssembly.instantiateStreaming.
С помощью вызова функции result.instance.exports._getOffset()
получаем смещение или адрес массива numbers, котоый определен в коде Си.
Затем этот адрес используется для создания массива numbers. То есть фактически массив numbers в javascript представляет массив numbers, который опредлен в коде Си.
Далее происходит наполнение этого массива и вызов функции result.instance.exports._sum
для вычисления суммы элементов массива.
Скомпилируем код WebAssembly с помощью следующей команды:
emcc hello.c -O2 -s WASM=1 -s ONLY_MY_CODE=1 -s EXPORTED_FUNCTIONS="['_sum', '_getOffset']" -o hello.js
И запустим страницу index.html на выполнение следующей командой:
emrun index.html