Я впервые услышал о WebAssembly во время просмотра Google I/O ’17. WebAssembly (wasm) позволяет разработчикам компилировать C/C++ или другие статически типизированные языки в JavaScript для создания высокопроизводительных веб-приложений. Предположим, у меня есть библиотека обнаружения штрих-кода C/C++ или библиотека OCR, развернутая на стороне сервера, теперь я могу переместить ее на сторону веб-клиента. Я был в восторге от этой функции и хотел узнать больше о соответствующих технологиях. Мой первый шаг — изучить компилятор, вызывающий Emscripten.

Что такое Эмскриптен

«Emscripten — это проект на основе LLVM, который компилирует C и C++ в высокооптимизируемый JavaScript в формате asm.js. Это позволяет вам запускать C и C++ в Интернете почти с естественной скоростью без плагинов».

Прочитав определение, вы могли заметить, что в нем упоминается не WebAssembly, а asm.js. По умолчанию Emscripten компилирует код C/C++ в файл asm.js, совместимый с большинством веб-браузеров. Принимая во внимание, что WebAssembly включает двоичный файл wasm и связующий файл JavaScript:

Веб-браузеры, поддерживаемые WebAssembly:

  • Фаерфокс 52+
  • Хром 57+
  • Последняя опера
  • Firefox 47+: включите флаг options.wasm в about:config
  • Chrome 51+: включите экспериментальный флаг WebAssembly в chrome://flags

Загрузка и установка

Начиная

Создайте файл hello.c:

#include <stdio.h>
int main() {
printf("hello\n");
return 0;
}

Скомпилируйте код C/C++:

emcc hello.c

Создан файл a.out.js. Мы можем запустить его с помощью Node.js:

node a.out.js

Чтобы встроить код в веб-страницу, используйте -o:

emcc hello.c -o hello.html

Эта команда создает файл hello.js и файл hello.html. Откройте HTML-страницу в веб-браузере:

Файл a.out.js и файл hello.js одинаковы:

Давайте попробуем вариант wasm, чтобы увидеть разницу:

emcc hello.c -s WASM=1 -o hello2.html

Что-то здесь не так:

невозможно использовать WASM=1, если полная проверкаasm.jsотключена (убедитесь, что вы запустили как минимум -O1 и ищите предупреждения о других параметрах, которые могут заставитьasm. jsвыключено).

Добавьте опцию -O, чтобы исправить ошибку:

Эта команда создает пять новых файлов:

Если дважды щелкнуть файл hello2.html в Chrome, вы ничего не увидите:

Причина в том, что файл hello2.wasm нужно загружать через XHR, а Chrome не поддерживает запросы file:// XHR. Вместо этого вы можете использовать Firefox или развернуть страницу на сервере:

emrun hello2.html

Вызов API C/C++

Emscripten устраняет мертвый код, чтобы минимизировать размер кода. Поэтому нам нужно экспортировать функции, которые будут вызываться JavaScript. Есть два способа экспортировать собственные функции.

Экспорт функций по командной строке

Напишите код C:

#include <stdio.h>
#include <emscripten.h>
char* world() {
return "world";
}
int main() {
printf("hello\n");
return 0;
}

Скомпилируйте код:

emcc -s EXPORTED_FUNCTIONS="['_world']" hello.c -o hello.html

Добавьте следующий код в hello.html:

<button onclick="native()">click</button>
<script type='text/javascript'>
function native() {
var content = Module.ccall('world', 'string');
alert(content);
}
</script>

Вы также можете создать файл app.js для запуска с Node.js:

var hello_module = require('./hello.js');
console.log(hello_module.ccall('world', 'string'));

Определите функцию с помощью EMSCRIPTEN_KEEPALIVE

Изменить функцию:

char* EMSCRIPTEN_KEEPALIVE world() {
return "world";
}

Скомпилируйте файл hello.c:

emcc hello.c -o hello.html

При нажатии на кнопку вы можете увидеть сообщение об ошибке: Ошибка утверждения: среда выполнения была прервана (используйте NO_EXIT_RUNTIME, чтобы сохранить ее в рабочем состоянии после выхода из main()).

Чтобы решить эту проблему, добавьте emscripten_exit_with_live_runtime() в основную функцию:

int main() {
printf("hello\n");
emscripten_exit_with_live_runtime();
return 0;
}

Или вы можете скомпилировать код с помощью -s NO_EXIT_RUNTIME=1:

emcc hello.c -o hello.html -s NO_EXIT_RUNTIME=1

Предварительно загрузить файлы ресурсов

Используя emcc, мы можем легко упаковать файлы в файл .data во время компиляции. Предположим, у вас есть текстовый файл лицензии, расположенный в папке с активами. Запустите команду:

emcc hello.c -o hello.html --preload-file ./asset

Создайте новую функцию для чтения файла:

void getLicense() {
char license[256];
int index = 0;
FILE *file = fopen("./asset/license.txt", "r");
if (!file) {
printf("cannot open file\n");
return;
}
while (!feof(file)) {
char c = fgetc(file);
if (c != EOF) {
license[index++] = c;
}
}
fclose (file);
printf("%s\n", license);
}

Статус инициализации модуля

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

setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.style.display = 'none';
}
statusElement.innerHTML = text;
}

Если вы отлаживаете фрагмент, вы обнаружите, что когда статус меняется на «Выполняется», модуль загружен. Это кажется немного сложным. Лучший способ — отправить уведомление из функции main() в функцию JavaScript:

int main() {
EM_ASM(onLoaded());
return 0;
}

Определите onLoaded() в custom.html:

<script>
// called from main()
function onLoaded() {
alert('Module is loaded');
}
</script>

Если у вас нет функции main(), также работает использование onRuntimeInitialized():

<script>
// called when the runtime is ready
var Module = {
onRuntimeInitialized: function () {
alert('onRuntimeInitialized');
}
};
</script>

Вот окончательная пользовательская HTML-страница:

<!doctype html>
<html>
<head>
</head>
<body>
<h1>Custom Page</h1>
<button onclick="native()">click</button>
<script>
// called when the runtime is ready
var Module = {
onRuntimeInitialized: function () {
alert('onRuntimeInitialized');
}
};
// called from main()
function onLoaded() {
alert('Module is loaded');
}
function native() {
var content = Module.ccall('world', 'string');
alert(content);
}
</script>
<script async type="text/javascript" src="hello.js"></script>
</body>
</html>

Ресурсы

Исходный код

https://github.com/yushulx/asmjs

Первоначально опубликовано на www.codepool.biz 24 июля 2017 г.