WebAssembly 快速起步
撰写时间:2024-02-25
修订时间:2025-01-26
概述
互联网真是越来越强大了。可曾想过,在Web上面也能运行C语言编写的桌面应用程序?
没用过 Mac 电脑?这里能让您在Web网页中直接操作一台1984年的 Mac 电脑。
WebAssembly(Wasm)是一种虚拟机,我们可将其部署到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等。
Emscripten的API甚至提供了OpenGL及SDL2的支持。应用效能?快,几乎与原生代码一样快。著名的Unreal, Unity等3D引擎均成功地移植到WebAssembly。Emscripten的Porting Examples and Demos列出了许多其移植过的应用程序,诸如Quake, Doom, Dune等著名的游戏,均可在线免费玩。除此之外,还有一些图形库、编程语言在线编译等,有时间的话多去挖掘。
Mac下安装Emscripten
打开终端,运行下面命令:
在用户目录下新建一个WebAssembly/Emscripten目录并转入到该目录中。
运行:
将在Emscripten目录下新建一个名为emsdk的目录存放所下载的内容。运行:
至此,Emscripten安装完毕。
编译Hello.c
运行:
此时文件路径如下图所示,且当前目录位于MySrc中。
- Users
- sarkuya
- WebAssembly
- Emscripten
- emsdk
- MySrc
- hello.c
- Emscripten
- WebAssembly
- sarkuya
下面,为当前目录设置emsdk的编译环境:
我们可以在终端中调用env命令
来查看环境变量。其变化一是添加了两个PATH路径:
这样,在当前目录下,我们可以直接调用上面两个目录下的命令了。二是设置了以下环境变量:
使用您的IDE打开并编辑Hello.c文件,其内容如下:
注:printf的输出内容,记得在末尾加上换行符\n
,否则默认情况下会出现输出流未关闭
的错误警示。
在终端中运行:
在当前目录下生成a.out.js及a.out.wasm两个文件。后者是二进制文件,由前者调用。终端输入:
终端输出:
这意味着,我们的C语言程序转换为JavaScript程序,并被node运行了!
接着,生成可在Web下运行的HTML文件。
将在当前目录下生成hello.html, hello.js及hello.wasm三个文件。
运行HTML文件
将这三个文件部署至服务器中,运行hello.html,效果如下:
两个黑框。C语言中的输出字符串Hello World
,在下面的黑框中打印出来了。
上面的黑框是Canvas!尽管本例尚未使用它,但这并未妨碍您的大脑此刻应充满无限想像了!
别急,我们来个更简易版的。新建一个my-hello.html,内容如下:
运行my-hello.html。hello.c的字符串输出至my-hello.html网页最后一个段落中了。我们成功地将C语言程序请至Web网页安家了。
代码分析
my-hello.html的主要内容:
有两个JavaScript语句块。
第一个JavaScript语句块主要定义了一个Module对象。这是Emscripten固定使用的一个对象,在程序运行的各个阶段,Emscripten将有序调用该对象的一些方法,如,在准备输出C语言中的:
hello.js将回调Module的print方法。相类似的,在输出C语言的错误时,将回调printErr方法。
而我们所需做的,就是在加载hello.js文件前,先为Module配置好这两个方法,让其按我们的意图将所传过来的文本输出至网页中。
然后,我们才能在上面的第二个JavaScript语句块中继续加载hello.js文件。这个文件很长,因为它要进行诸如内存管理、环境初始化、打扫战场等脏活。但最主要的,它先读取我们所设置的Module对象的配置:
当遇到C语言中的printf
语句时,在其内部将调用上面的printChar方法,而该方法又调用了Module的print方法及printErr方法。这样,C语言与HTML的桥梁就搭建起来了。
有选择的编译
生成3个文件
默认情况下,使用:
将生成output.html, output.js及output.wasm共3个文件。
生成.js及.wasm文件
使用:
将只生成output.js及output.wasm共2个文件。
当我们已经生成.html文件,而又需要经常修改.c源代码,可使用上述方法。
生成ES6 module 文件:
注:虽然表面上看,.mjs的扩展名确实很好地反映了该文件是JavaScript module文件,但在Web环境下,它需要进一步地配置服务器,以让服务器在遇到.mjs时在Content-Type部分为其设置为text/javascript
的MIME type. 否则,客户端将报错且不能执行该JavaScript文件。然而,现在众多服务器只是为.js正确地设置了MIME type,但并未为.mjs设置正确的MIME type. 它们要么返回空的,要么返回text/plain
,导致客户端报错。而服务器端的配置往往超出了我们的能力和范围。因此,在Web环境下,建议只使用.js的扩展名。
生成.wasm文件
使用:
将仅生成output.wasm1个文件。
注意:一旦.c源代码改变,应参照上一步,至少同时生成output.js及output.wasm这2个文件。
使用HTML模板
可以使用我们自己的HTML页面作为模板。
例如,若将上面的my-hello.html作为模板,只需将其主要内容改为:
{{{ SCRIPT }}}
是一个专用的占位符,Emscripten将在模板网页中查找此标志,并替换为:
然后,调用命令:
将生成using-template.html, using-template.js及using-template.wasm共3个文件。
EST与JS的通讯
在C环境中调用Web端
方法1:
上面的#ifdef...#endif
也可以不要。效果是,一加载网页后,立即弹出窗口。
EM_JS
用以在.c文件中声明JavaScript函数。
方法2:
可以直接在C语言中调用console, document等对象,这就很方便了。但前提是,这些对象必须存活于EM_JS
或EM_ASM
环境中。
上面这两种方式都称为inline JavaScript。
将EST变量值向JS传递:
$0
是EST的语法,而`${}`
是JS的语法,组合在一起,非常和谐。
将JS变量值向EST传递:
接受返回值时,使用EM_ASM_INT
宏。EST先向JS传递10的值,JS加30后,再回传给EST。EST再加30。最后这两个家伙再在网页的不同区域分别打印各自的值。
在使用EM_ASM
宏的时候,不能使用双引号"
。否则在JavaScript的console中将报错。
方法3:
EST的emscripten_run_script函数通过调用JS的eval函数来执行。
小结
Emscripten所生成的代码虽然很长,但值得玩味。例如,在hello.html文件中,就有一个使用CSS的animation及HTML5的progress标签所实现的转圈等待动画效果。而hello.js文件则应用了多种设计模式及技巧。
最主要的,当C语言等代码可以无比方便地移植至Web环境时,通往世界的窗户一下子就打开了。