Передача данных между JavaScript и WebAssembly

Последнее обновление: 30.07.2019

Передавать данные между кодом 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
WebAssembly.Memory и передача данных в wasm Массивы в WebAssembly
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850