概述:
WebAssembly简称WASM,是一种新的编码方式,类似于汇编语言,可以接近原生性能来在web上运行,是一个可移植、体积小、加载快并且兼容 Web 的全新格式,他有几个特点
- 快速、高效、可移植——通过利用常见的硬件能力,WebAssembly代码在不同平台上能够以接近本地速度运行。
- 可读、可调试——WebAssembly是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准即将得到最终版本),这允许通过手工来写代码,看代码以及调试代码。
- 保持安全——WebAssembly被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
- 不破坏网络——WebAssembly的设计原则是与其他网络技术和谐共处并保持向后兼容。
WebAssembly是一门低级的类汇编语言。它有一种紧凑的二进制格式,使其能够以接近原生性能的速度运行,并且为诸如C++
和Rust
等拥有低级的内存模型语言提供了一个编译目标以便它们能够在网络上运行。
目前WebAssembly已经在大多数浏览器中支持,并且在逐步增加和维护中。
实现原理:
传统的JavaScript代码在浏览器内是在虚拟机中运行,利用JavaScript高级语言能解决通常web场景下绝大多数问题,但是遇到如网络、3D、AR、VR等技术,JavaScript的性能瓶颈就凸显出来了,而且浏览器需要加载JavaScript并解析运行,也是需要消耗很大的资源,因此WebAssembly的能力就体现出来了。
新的虚拟机将会同时加载JavaScript和WebAssembly两种,两种也会采用API互相调用:
graph TD
浏览器 --> JavaScript虚拟机
JavaScript虚拟机 --> JavaScript
JavaScript虚拟机 --> WebAssembly
当然,WebAssembly不仅仅局限于web环境,非web环境下(比如Node环境)也是他支持的范围,任何支持WebAssembly的环境需提供以下支持:
- 8位字节。
- 内存可按字节维度寻址。
- 支持未对齐的内存访问或因软件仿真导致的已知缺陷。
- 32位有符号整数有两个补码,64位可选。
- IEEE 754-2008 32位和64位浮点数,除了一些例外。
- 小端字节序。
- 可使用32位指针或索引有效处理内存区域。
- WebAssembly64 使用64位指针或索引额外支持大于4GB的线性内存。
- 执行在同一机器上的WebAssembly模块与其他模块或进程强制安全隔离
- 提供一个进程担保所有线程的执行的执行环境(即便它们以非并行方式执行)
- 访问8、16、32位自然对齐内存时,可用无锁原子运算符。至少也要包含一个原子级的比较和交换运算符(或同等的加载连接/条件存储)。
- WebAssembly64 访问64位自然对齐内存时,额外需要无锁原子运算符。
Emscripten介绍
在web端执行C/C++代码,需要有工具将代码编译成.wasm(WebAssembly执行模块)文件,这类工具有很多,目前比较成熟的是Enscripten。
Emscripten工具能够将一段C/C++代码,编译出:
- 一个.wasm模块
- 用来加载和运行该模块的JavaScript”胶水“代码
- 一个用来展示代码运行结果的HTML文档 工作流程如下:
这里以Mac电脑示例流程:
安装
git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh #注册环境变量
编写测试代码
mkdir hello
cd hello
touch hello.c
然后用编辑器打开hello.c贴上如下代码:
#include <stdio.h>
int main(int argc, char ** argv) {
printf("Hello, world!");
}
编译
emcc ./hello.c -s WASM=1 -s EXIT_RUNTIME=1 -o hello.html
这里解释一下:
- WASM=1 :表示生成wasm格式文件而不是asm格式文件,目前新版本默认就是wasm
- EXIT_RUNTIME=1:编译出的wasm默认情况下不会退出运行的,这是web情况下期待的方式,主程序main虽然运行结束了,但模块没有退出,静态变量可以保持在内存中,不释放。同时标准 I/O 缓冲区没有被flush,加此参数则能让模块结束,才能看到I/O输出,否则无法看到printf的输出
- o:输出文件格式
- O:是用来指定优化级别,优化级别有 -O0, -O1, -O2, -O3 -Os这五种级别。不指定是为 -O0, 即没有优化,开发时一般指定为 -O0 或 -O1, 这样编译速度快,调试方便。 正式发布时可以是 -O2 或 -O3,这时代码会优化,执行更快。-Os 不光是执行快,同时优化大小,可生成更小的执行文件。 执行后目录下会生成hello.html hello.js 和hello.wasm三个文件。
运行
大多数浏览器无法使用file:// 来加载文件,所以需要启动一个web server,可以用node的 live-server,也可以采用emcc内置的web server
npm install live-server
live-server ./
或者
emrun --no_browser --port 8080 .
这样当打开浏览器时,就能看到我们的Hello world了:
分析
至此,一个最基础的C/C++代码运行到了Web环境,我们反过来看下hello.html hello.js 和hello.wasm三个文件。
- hello.wasm 二进制的wasm模块代码
- hello.js 一个包含了用来在原生C函数和JavaScript/wasm之间转换的胶水代码的JavaScript文件
- hello.html 一个用来加载,编译,实例化你的wasm代码并且将它输出在浏览器显示上的一个HTML文件
JavaScript与C/C++交互
指定自己的模板html文件
当前目录中创建一个放置自己html模板文件的文件夹
mkdir html_template
然后在 emsdk 中搜索一个叫做 shell_minimal.html 的文件,然后复制它到刚刚创建的目录下的 html_template 文件夹内
cp ~/emsdk/upstream/emscripten/src/shell_minimal.html html_template
这样在原有编译命令中用--shell-file html_template/shell_minimal.html
指定模板文件即可
emcc ./hello.c -s WASM=1 -s EXIT_RUNTIME=1 --shell-file html_template/shell_minimal.html -o hello.html
重新启动web server即可看到实际运行效果
改写hello.c
#include <stdio.h>
#include <emscripten/emscripten.h>
int main(int argc, char ** argv) {
printf("Hello World\n");
}
#ifdef __cplusplus
extern "C" {
#endif
int EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
printf("我的函数已被调用\n");
}
#ifdef __cplusplus
}
#endif
默认情况下emscripten只会生成main函数,如果想要用指定函数,则需要指定EMSCRIPTEN_KEEPALIVE
但需要导入emscripten.h
emcc ./hello.c -s WASM=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']" --shell-file html_template/shell_minimal.html -o hello.html
这里我们增加一个新的参数"EXPORTED_RUNTIME_METHODS=['ccall']"
,并且去掉了-s EXIT_RUNTIME=1
,程序不能退出,需要一直在执行。
增加交互按钮
打开我们的shell_minimal.html 文件,在其中增加一个按钮:
然后在<script>
和</script>
中注册响应函数
重新运行
emcc
命令生成新的html,重启web server,这样页面就会出现一个按钮, 点击就会看到我的函数已被调用
,并且控制台也可以看到对应的msg
WebAssembly 的作用
WebAssembly基于其良好的性能,可以很方便的加强web能力,可以体验一下官方基于unity来写的一个demo www.wasm.com.cn/demo/Tanks/ 目前主流浏览器和node的支持情况:
相信WebAssembly会为web提供更多的可能
- web支持FFmpeg
- web支持OpenAL
- web支持OpenCV
- ...
后续文章计划
- WebAssembly-C调用JavaScript API
- WebAssembly-手动编写WebAssembly脚本
欢迎大家关注支持