WebGL Tutorial
and more

WebAssembly 快速入门

撰写时间:2024-02-25

修订时间:2024-02-25

概述

互联网真是越来越强大了。可曾想过,在Web上面也能运行C语言编写的桌面应用程序吗?

WebAssemblyWasm)是一种虚拟机,我们可将其部署到Web, Node.js, WebAssembly Runtime等平台上运行。目前的主流浏览器,如Chrome, Safari, Firefox等,均很好地支持WebAssembly

Wasm是Web标准,有其标准化社团,目前共有1656个成员,尚在招人。

Emscripten

Emscripten是一个开源的编译工具,可将使用C, C++等LLVM所支持的编程语言来编写的应用程序编译为WebAssembly

Emscripten所支持的编程语言包括:Rust, C, C++, Python, .NET, Go, Ruby等。

digraph { rankdir=TB; {rank=same; Emscripten, hw, WebAssembly} {rank=same; user, Web} hw [label="App.c" shape=note]; Web [shape=cylinder]; Emscripten -> hw [label="compile"]; hw -> WebAssembly [label="into"]; WebAssembly -> Web [label="depoly in"]; user -> Web [label="access to"]; }

Emscripten的API甚至提供了OpenGL及SDL2的支持。应用效能?快,几乎与原生代码一样快。著名的Unreal, Unity等3D引擎均成功地移植到WebAssembly。Emscripten的Porting Examples and Demos列出了许多其移植过的应用程序,诸如Quake, Doom, Dune等著名的游戏,均可在线免费玩。除此之外,还有一些图形库、编程语言在线编译等,有时间的话多去挖掘。

Mac下安装Emscripten

打开终端,运行下面命令:

cd ~ mkdir WebAssembly cd WebAssembly mkdir Emscripten cd Emscripten

在用户目录下新建一个WebAssembly/Emscripten目录并转入到该目录中。

运行:

git clone https://github.com/emscripten-core/emsdk.git

将在Emscripten目录下新建一个名为emsdk的目录存放所下载的内容。运行:

cd emsdk ./emsdk install latest ./emsdk activate latest

至此,Emscripten安装完毕。

编译Hello.c

运行:

cd .. mkdir MySrc cd MySrc touch hello.c

此时文件路径如下图所示,且当前目录位于MySrc中。

  • Users
    • sarkuya
      • WebAssembly
        • Emscripten
          • emsdk
          • MySrc
            • hello.c

下面,为当前目录设置emsdk的编译环境:

source ../emsdk/emsdk_env.sh

我们可以在终端中调用env命令

env

来查看环境变量。其变化一是添加了两个PATH路径:

PATH= /Users/sarkuya/WebAssembly/Emscripten/emsdk: /Users/sarkuya/WebAssembly/Emscripten/emsdk/upstream/emscripten:

这样,在当前目录下,我们可以直接调用上面两个目录下的命令了。二是设置了以下环境变量:

EMSDK=/Users/sarkuya/WebAssembly/Emscripten/emsdk EMSDK_NODE=/Users/sarkuya/WebAssembly/Emscripten/emsdk/node/16.20.0_64bit/bin/node EMSDK_PYTHON=/Users/sarkuya/WebAssembly/Emscripten/emsdk/python/3.9.2_64bit/bin/python3

使用您的IDE打开并编辑Hello.c文件,其内容如下:

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

在终端中运行:

emcc hello.c

在当前目录下生成a.out.jsa.out.wasm两个文件。后者是二进制文件,由前者调用。终端输入:

node a.out.js

终端输出:

Hello World

这意味着,我们的C语言程序转换为JavaScript程序,并被node运行了!

接着,生成可在Web下运行的HTML文件。

emcc hello.c -o hello.html

将在当前目录下生成hello.html, hello.jshello.wasm三个文件。

运行HTML文件

将这三个文件部署至服务器中,运行hello.html,效果如下:

Hello Wasm

两个黑框。C语言中的输出字符串Hello World,在下面的黑框中打印出来了。

上面的黑框是Canvas!尽管本例尚未使用它,但这并未妨碍您的大脑此刻应充满无限想像了!

别急,我们来个更简易版的。新建一个my-hello.html,内容如下:

<body> <article> <h1>Hello, WebAssembly</h1> <p>Looking for the place to hold:</p> <trim-pre><esc-code escaped> printf("Hello World\n"); </esc-code></trim-pre> <p>from the C language?</p> <p>See below:</p> <p id="output"></p> </article> <script> var Module = { 'print': function(text) { document.getElementById('output').innerText = text; }, 'printErr': function(text) { console.log(text); } }; </script> <script async type="text/javascript" src="hello.js"></script> </body>

运行my-hello.htmlhello.c的字符串输出至my-hello.html网页最后一个段落中了。我们成功地将C语言程序请至Web网页安家了。

代码分析

my-hello.html的主要内容:

<script> var Module = { 'print': function(text) { document.getElementById('output').innerText = text; }, 'printErr': function(text) { console.log(text); } }; </script> <script async type="text/javascript" src="hello.js"></script>

有两个JavaScript语句块。

第一个JavaScript语句块主要定义了一个Module对象。这是Emscripten固定使用的一个对象,在程序运行的各个阶段,Emscripten将有序调用该对象的一些方法,如,在准备输出C语言中的:

printf("Hello World\n");

hello.js将回调Moduleprint方法。相类似的,在输出C语言的错误时,将回调printErr方法。

而我们所需做的,就是在加载hello.js文件前,先为Module配置好这两个方法,让其按我们的意图将所传过来的文本输出至网页中。

然后,我们才能在上面的第二个JavaScript语句块中继续加载hello.js文件。这个文件很长,因为它要进行诸如内存管理、环境初始化、打扫战场等脏活。但最主要的,它先读取我们所设置的Module对象的配置:

var Module = typeof Module != 'undefined' ? Module : {}; var out = Module['print'] || console.log.bind(console); var err = Module['printErr'] || console.error.bind(console); var printChar = (stream, curr) => { var buffer = printCharBuffers[stream]; assert(buffer); if (curr === 0 || curr === 10) { (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0)); buffer.length = 0; } else { buffer.push(curr); } };

当遇到C语言中的printf语句时,在其内部将调用上面的printChar方法,而该方法又调用了Moduleprint方法及printErr方法。这样,C语言与HTML的桥梁就搭建起来了。

小结

Emscripten所生成的代码虽然很长,但值得玩味。例如,在hello.html文件中,就有一个使用CSS的animation及HTML5progress标签所实现的转圈等待动画效果。而hello.js文件则应用了多种设计模式及技巧。

最主要的,当C语言等代码可以无比方便地移植至Web环境时,通往世界的窗户一下子就打开了。

参考资源

  1. webassembly.org
  2. emscripten.org
  3. MDN WebAssembly