第1章 Emscripten 快速入门

1.1 使用emsdk命令行工具安装Emscripten

Emscripten包含了将C/C++代码编译为WebAssembly所需的完整工具集(LLVM、Node.js、Python、Java等),它不依赖于任何其他的编译器环境。

emsdk是一组基于Python 2的脚本,Mac系统自带Python,无需安装

1.1.1 新建一个demo文件夹,然后通过git下载安装工具包

git clone https://github.com/juj/emsdk.git

1.1.2 安装并激活

进入到安装包的文件夹,分别执行以下命令

git pull
./emsdk install latest
./emsdk activate latest

把emcc命令添加至环境变量,新建一个控制台,切换至emsdk所在的目录,执行以下命令:

source ./emsdk_env.sh

注意:每次重新登陆或者新建 Shell 窗口,都要执行一次这行命令source ./emsdk_env.sh,然后本控制台窗口才能使用emcc命令。

1.1.3 验证安装是否成功

emcc是Emscripten的核心命令,正确安装激活并设置环境变量后,执行emcc -v可以查看版本信息:

> emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.17
clang version 13.0.0 (/opt/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 3b677b81cec7b3c5132aee8fccc30252d87deb69)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/liuxiaofan/WorkSpace/vipkid/emscripten/emsdk/upstream/bin

1.2 Hello, wolrd!

先跑一个最基本的demo

1.2.1 生成wasm

新建一个名为hello.cc的C源文件,为了正确标识中文字符串,将其保存为UTF8编码:

//hello.cc
#include <stdio.h>

int main() {
    printf("Hello, wolrd!");
    return 0;
}

执行编译命令生成wasm文件:

emcc hello.cc

目录下将得到两个文件:a.out.wasm以及a.out.js。其中a.out.wasm为C源文件编译后形成的WebAssembly汇编文件;a.out.js是Emscripten生成的胶水代码。

使用-o选项可以指定emcc的输出文件,执行下列命令:

emcc hello.cc -o hello.js

注意:现在如果改变了hello.cc文件再次编译的话会报错。因为编译出的wasm默认情况下不会退出运行时,所以每次编译的时候加上-s EXIT_RUNTIME=1 即可。如下:

emcc index.cc -o index.js -s EXIT_RUNTIME=1

1.2.2 在网页中测试
新建一个index.html的网页文件:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Emscripten:你好,世界!</title>
  </head>
  <body>
    <script src="hello.js"></script>
  </body>
</html>

本地起一个web服务,让localhost的目录指向本demo的目录,然后打开控制发现成功打印出来Hello, wolrd!
例如全局安装live-server,然后执行live-server –port=8888启动一个本地服务

npm i -g live-server
live-server --port=8888

1.2.3 在Node.js中测试

Emscripten自带了Node.js环境,因此我们可以直接使用node来测试刚才的程序:

> node hello.js
Hello, wolrd!

1.3 胶水代码初探

简单了解一下Emscripten生成的JavaScript胶水代码hello.js

1.3.1 WebAssembly汇编模块载入

WebAssembly汇编模块(既.wasm文件)的载入后经过一系列的操作,最终被实例化,然后赋值了给全局对象Module的子对象asm。

function receiveInstance(instance, module) {
      ... ...
      Module['asm'] = exports;
      console.log(Module['asm']);  //我们在这添加一行Log在控制台里面打印出来看看这个对象里面都有哪些属性和方法
      ... ...

我当前的版本打印出来的结果是这样的,以后随着版本的升级可能会有变化。注意里面有个main方法

1.3.2 导出函数封装

在Emscripten中,C函数导出时,函数名前会添加下划线“_”,由此可知上述代码分别提供了main()以及malloc()函数的封装;我们可以在浏览器控制台中手动执行_main()以及Module._main()对此进行检验:如果报错,请忽略,接着往下看就明白了。

1.3.3 异步加载

经过我的测验,直接在控制台中执行_main()以及Module._main()会报错

Assertion failed: you need to wait for the runtime to be ready (e.g. wait for main() to be called)

解决的基本思路是在Module初始化前,向Module中注入一个名为onRuntimeInitialized的方法,Emscripten的Runtime就绪后,将会回调该方法。

<body>
    <script>
      Module = {};
      Module.onRuntimeInitialized = function() {
        //do sth.
        Module._main();
      }
    </script>
    <script src="hello.js"></script>
  </body>

如上面的代码,先监听RuntimeInitialized,再执行Module._main()就好了。