Я впервые услышал о 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>
Ресурсы
- http://kripken.github.io/emscripten-site/index.html
- http://webassembly.org
- https://developer.mozilla.org/en-US/docs/WebAssembly
Исходный код
https://github.com/yushulx/asmjs
Первоначально опубликовано на www.codepool.biz 24 июля 2017 г.