初识WebAssembly

近期线上收实验报告的时候,学生们上传的图片乱七八糟的。后期提示使用 扫描王 等软件处理后再上传效果好了很多。但无疑这给学生了带来了相应的繁琐。于是:如何在WEB能快速的处理图片,并实时的显示效果成为了新的需求。

首先,我们可以点击demo感受一下它的魅力。

而处理图片往往都在后端执行,直接在 WEB 处理则需要一个叫WebAssembly的知识,简单来说就是浏览器允许运行二进制的文件,而这个二进制的文件则是各种原后端语言通过编译器编译出来的。

所以可以用C++来写一个图片处理程序,并使用WebAssembly把它应用到浏览器中便成了解决方案。

本文在macos下,演示如何把Hello world运行在浏览器中。

Emscripten

要想把C++源码编译成浏览器可以运行的 WebAssembly , 则需要一些编译器,而Emscripten则属于其中的一个。

docker(推荐)

docker无疑是最简单的安装方式,官方image提供了多个版本供我们选择。

初识WebAssembly_第1张图片

我们在使用前仅仅需要下载相应的image即可,比如我们下载最新的版本:

 % docker  pull emscripten/emsdk

然后我们进行文件夹映射,并执行容器中的emcc命令即可,比如:

docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) \
  emscripten/emsdk emcc helloworld.cpp -o helloworld.js

则表示将当前路径下的helloworld.cpp编译成helloworld.js

macos

安装 Emscripten 需要从github下载相当的代码,并执行相应的操作,

环境要求:

  1. macOS 10.14 Mojave及以上
  2. 安装 Xcode Command Line Tools
  3. 安装git
  4. 安装cmake

命令如下:

# 下载代码
$ git clone https://github.com/emscripten-core/emsdk.git --depth=1
# 进入下载的文件夹
$ cd emsdk
# 执行安装命令,由于这个操作会从网下下载相应的第三方安装包,所以这可能需要一个比较友好的网络
$ ./emsdk install latest
# 源活我们刚刚安装的 latest 版本
$ ./emsdk activate latest
# 源活环境变量,每启动一新的shell,都要执行一次
$ source ./emsdk_env.sh

验证:

创建以下文件:

#include 

int main() {
    printf("Hello World\n");
    return 0;
}

验证

我们新建hello.c文件

#include 

int main() {
    printf("Hello World\n");
    return 0;
}

然后执行emcc hello.c -o hello.html

panjie@panjies-Mac-Pro src $ emcc hello.c -o hello.html
shared:INFO: (Emscripten: Running sanity checks)
cache:INFO: generating system asset: symbol_lists/ed436b369ffc02205671a0a9df422f9da2cf641b.txt... (this will be cached in "/Users/panjie/github/emscripten-core/emsdk/upstream/emscripten/cache/symbol_lists/ed436b369ffc02205671a0a9df422f9da2cf641b.txt" for subsequent builds)
cache:INFO:  - ok

然后我们就得到了 一个 html 文件,一个js文件以及一个wasm文件:

panjie@panjies-Mac-Pro src % ls
hello.c        hello.html    hello.js    hello.wasm

接着我们起一个http-server,并在浏览器中查看效果:

panjie@panjies-Mac-Pro src % http-server
Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8081
  http://192.168.0.242:8081
Hit CTRL-C to stop the server

初识WebAssembly_第2张图片

如果你是用的docker,则可以如下执行:

panjie@panjies-Mac-Pro src % docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc hello.c -o hello.html      
cache:INFO: generating system asset: symbol_lists/ed436b369ffc02205671a0a9df422f9da2cf641b.txt... (this will be cached in "/emsdk/upstream/emscripten/cache/symbol_lists/ed436b369ffc02205671a0a9df422f9da2cf641b.txt" for subsequent builds)
cache:INFO:  - ok

最后的效果是一致的。

分析

.c文件编译后生成了3个新的文件,html文件用于展示页面并且调用js文件,js文件则充当获取二进制文件,装载二进制文件,调用二进制文件并获取返回值的目的,而wasm则是浏览器直接执行的二进制文件。该文件由c语言编译而来,可以兼顾功能与效率,重要的是原本一些只能支持在应用程序中的功能,可以被移植到浏览器中来了。

编译至指定模板

Emscripten的github源码中,为我们提供了自定义的html模板,下面我们将 HelloWorld输入到这个自定义的模板中。

首先我们在当前文件夹中建立子文件夹html_template,并将位于Emscripten的github源码文件夹中的 /upstream/emscripten/src/shell_minimal.html复制到html_template文件夹。

panjie@panjies-Mac-Pro src % tree
.
├── hello.c
├── hello.html
├── hello.js
├── hello.wasm
└── html_template
    └── shell_minimal.html

1 directory, 5 files

接着我们执行如下命令:

panjie@panjies-Mac-Pro src %  docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc  -o hello1.html hello.c -O3 --shell-file html_template/shell_minimal.html
cache:INFO: generating system asset: symbol_lists/812dbbffa7488aec7a503446fae422688638f439.txt... (this will be cached in "/emsdk/upstream/emscripten/cache/symbol_lists/812dbbffa7488aec7a503446fae422688638f439.txt" for subsequent builds)
cache:INFO:  - ok

注意,上面的命令中O3不是03.

此时,便会使用模模板html_template/shell_minimal.html来生成hello1.html,使用http-server起个服务后查看结果如下:

初识WebAssembly_第3张图片

如果我们将当前的网络模拟成慢速3G:

image.png

则会发下如下启动过程:

先下载
初识WebAssembly_第4张图片

再准备
初识WebAssembly_第5张图片

最后才是呈现结果
初识WebAssembly_第6张图片

请求的时序如下:

image.png

如果我们开启缓存,那么整体请求将会友好的多:

初识WebAssembly_第7张图片

自定义模板

学习 DEMO

通过 shell_minimal.html 模板的学习,我们简单的把关键的信息拿出来学习一下。首先是 CSS 样式部分,该部门主要用于控制页面显示,我们暂时略过。

初识WebAssembly_第8张图片

上图这个html基本上可以分为两个部分,第一部分是图像UI输出,第三部分是sheel控制台输出。比如我们的hello.c,并没有输出任何图像,而是直接打印了 Hello World,所以上述图像就输出了一个黑框框。

在模板中,用于输出图像的标签是canvas:

而用于输出shell信息的是textarea

而以下的script代码的作用起的是衔接的作用:加载.c,运行.c,输出.c的结果。

    

模板最后存在的{{{ SCRIPT }}}则用于替换为 js 文件的引用。如此,我们先声明了符合 WebAssembly 接口的对象 Module,然后引入了 js 文件,而js文件则会应用这个刚刚声明的Module。这样一来,WebAssembly的 JS 文件更与当前页面结合起来了。

自定义模板

为了验证前面的假设,我们下面来如下自定义模板并命名为sample.html,同是样存到html_template文件夹中:




    
    
    WebAssembly


    

这里是输出的内容

{{{ SCRIPT }}}

然后我们运行以下命令来将hello.c渲染进来:

docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc  -o sample.html hello.c -O3 --shell-file html_template/sample.html

最后我们将得到以sample打头的js html 以及 wasm 文件,运行http-server并查看:

初识WebAssembly_第9张图片

函数

最后我们再看看如何调用.cpp文件中的函数,我们简单用c++语言写个求开平方,并把它应用html页面中。官方文档指出调用 C 语言中function最简单的方法便是使用 ccall以及cwrap.

ccall() 使用指定的参数来调用一个编译后的 C 函数,而cwrap()则是把 C 中的函数包裹成js的函数,然后再像调用普通的js函数一样来进行调用。所以如果我们只想调用一次,那么用ccall就好了,如果我们想多次调用,则建立使用cwarp来封装一下。

创建一个 sqrt.cpp 文件并加入以下代码:

#include 

// 兼容 C++
extern "C" {
    int int_sqrt(int x) {
      return sqrt(x);
    }
}

接下来我们将其编译为 js wasm文件,需要注意的是:

  1. 本次我们是先编译,然后再写模板,所以我们把目标文件设置为sqrt.js,而非sqrt.html
  2. 我们需要指定编译的方法,并以_打头
  3. 我们需要指定ccall、cwarp
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc sqrt.cpp -o sqrt.js -sEXPORTED_FUNCTIONS=_int_sqrt -sEXPORTED_RUNTIME_METHODS=ccall,cwrap

最终将生成 js 及 wasm 两种类型的文件。

最后,我们写个html代码来尝试调用一下:




    
    
    加法器


    
请输入:
结果:

初识WebAssembly_第10张图片

最后我们再测试下 cwrap :

最终实现效果相同。

总结

WebAssembly是个变革性的东西,有人说它将引领下一代WEB开发,它的出现使得原本仅能安装客户端才能实现在功能,当下可以直接在 WEB 端来使用了。同时由于其编译为2进制的特性,在保证了性能的同时能提供了足够的安全性。

你可能感兴趣的:(webassembly)