Emscripten之基础使用

一、前言

随着WebAssembly技术的发展,asm.js的竞争力已经逐渐被削弱了,所以Emscripten进一步与Binaryen结合,形成基于LLVM构建的WebAssembly后端。现如今,我们已经可以通过Emscripten工具链直接构建Wasm应用了。

有兴趣了解WebAssembly与asm.js的可以参考以下资料:

  • asm.js

阮一峰《asm.js和Emscripten入门教程》

  • WebAssembly

胡子大哈翻译的《WebAssembly系列(一) 生动形象地介绍WebAssembly》

二、编译目标与编译流程

在Emscripten环境下,编译目标可分为asm.js与WebAssembly两种,前者生成一个js文件,后者则生成wasm文件以及对应的JS胶水代码。因为WebAssmbly是二进制格式的,体积小、执行率高,在兼容性允许的条件下,一般建议试用WebAssembly作为编译目标。

使用emcc编译C/C++代码的主要流程如下:

Emscripten之基础使用_第1张图片

C/C++代码首先通过Clang编译成LLVM Bitcode,然后再根据不同的编译目标编译为asm.js或WebAssembly。

由于内部调用Clang,因此emcc支持绝大多数的Clang选项,比如-O、-g、-s OPTIONS=VALUE(这里是根据自己需求设置,比如编译为asm.js需要设置-s WASM=0)等。除此之外,为了适应Web环境,还增加了一些特有的选项,如–pre-js YOURFILE、–post-js YOURFILE等。

emcc所有的选项列表可以通过以下指令查询:

emcc --help

三、Emscripten的简单使用

首先我们编写一个简单的demo,保存为hello.c/hello.cc:

#include 

int main() {
    printf("Hello World!");
}

因为emsdk不是安装在系统上的,所以编译操作都需要在emsdk文件夹下进行。建议在emsdk下新建一个文件夹,存放需要编译的文件或项目。

3.1 生成默认Html

cd YourFolder
emcc hello.c -o hello.html

完成之后会在当前文件夹下生成hello.html、hello.js、hello.wasm三个文件。打开hello.html,可以看到默认的页面中显示“Hello World!”。如果想自定义html页面,可以只生成asm.js或者WebAssembly,然后在Web项目中引入使用即可。

3.2 生成asm.js

Emscripten是默认生成wasm文件的,如果想使用asm.js,需要设置WASM项为0。

emcc hello.c -s WASM=0 -o hello.js

编译之后只生成hello.js。但当编译目标为asm.js时,未对齐的内存读写可能会出现异常。关于Emscripten内存对齐的问题可以参考以下资料:《C/C++面向WebAssembly编程》。

3.3 生成WebAssembly

执行以下命令

emcc hello.c -o hello_wasm.js

编译之后生成hello.js与hello.wasm两个文件。其中hello.wasm是Web项目中使用的WebAssembly模块,而hello.js是对应的JS胶水代码,实现了将wasm文件引入,并导出对应的函数等。

四、JS中调用C函数

在Web项目中引入asm.js或WebAssembly,C/C++代码中的main函数在加载之后会立即运行。Emscripten除了可以导出main函数,还可以导出其他C/C++函数,但是为了避免在编译时被优化器删除普通的C/C++函数,所以需要在函数前加EMSCRIPTEN_KEEPALIVE标识,提前告知编译器保留当前函数。为了方便导出,提供以下函数导出宏:

#ifndef EM_PORT_API
#	if defined(__EMSCRIPTEN__)
#		include 
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#		else
#			define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#		endif
#	else
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype
#		else
#			define EM_PORT_API(rettype) rettype
#		endif
#	endif
#endif

修改之前的demo,保存为say.c,代码如下:

// 此处加上EM_PORT_API宏

#include 

EM_PORT_API(void) say_hello() {
    printf("Hello World!");
}

打开编译器,执行以下命令:

emcc say.c -o say.js

这里编译目标是WebAssembly,在Web项目中可以使用以下两种方式引入使用:

  • 4.1 script标签引入使用


<html>
    <head>
        <meta charset="utf-8">
        <title>Say Hellotitle>
    head>
    <body>
        <script>
            Module = {}
            Module.onRuntimeInitialized = function() {
                Module._say_hello();
            }
        script>
        <script src="say.js">script>
    body>
html>

在Module初始化前,向Module中注入一个名为onRuntimeInitialized的方法,Emscirpten中的Runtime就绪之后,就会回调这个方法。这里使用回调的原因是因为Emscripten生成的JS胶水代码中,是使用异步加载wasm文件的,所以需要通过该方法确认Runtime准备就绪后,才执行say_hello函数。

可以观察到,如果编译时生成了JS胶水代码,那么Module中对应的C/C++导出函数,在原函数名的基础上增加了下划线“_”前缀。

注意:JS胶水代码需要与wasm文件放在同一目录下。

  • 4.2 模块引入使用

使用require引入使用前,需要在JS胶水代码底部增加:

module.exports = Module;

在JS引入:

var MyModule = require("say.js")
MyModule.onRuntimeInitialized = function() {
    MyModule._say_hello();
}

你可能感兴趣的:(Emscripten)