写点什么

从 0 开始快速上手 WebAssembly:Emscripten 使用入门

  • 2020-09-09
  • 本文字数:9390 字

    阅读完需:约 31 分钟

从0开始快速上手WebAssembly:Emscripten使用入门

在上一篇文章《WebAssembly 如何演进成为“浏览器第二编程语言”?》中,我们较为详细地讲述了WebAssembly的演变历程,通过 WebAssembly 的演变历程,我们可以对 WebAssembly 的三个优点(二进制格式、Low-Level 的编译目标、接近 Native 的执行效率)有比较深刻的理解。


在本章中我们将选取 Emscripten 及 C/C++语言来简要讲述 WebAssembly 相关工具链的使用,通过较为简单的例子帮助大家更快速地上手 WebAssembly 相关的应用开发。请放心,在本章中我们将避免复杂难懂的 C/C++语言技巧,力求相关示例简单、直接、易懂。如果你有 Rust、Golang 等支持 WebAssembly 的相关语言背景,那么可以将本章相关内容作为参考,与对应官方工具链结合学习。

关于 Emscripten

Emscripten 是WebAssembly工具链里重要的组成部分。从最为简单的理解来说,Emscripten 能够帮助我们将 C/C++代码编译为 ASM.js 以及 WebAssembly 代码,同时帮助我们生成部分所需的 JavaScript 胶水代码。


但实质上 Emscripten 与 LLVM 工具链相当接近,其包含了各种我们开发所需的 C/C++头文件、宏参数以及相关命令行工具。通过这些 C/C++头文件及宏参数,其可以指示 Emscripten 为源代码提供合适的编译流程并完成数据转换,如下图所示:



Emscripten 编译流程(来自官网)


emcc 是整个工具链的编译器入口,其能够将 C/C++代码转换为所需要的 LLVM-IR 代码,Clang/LLVM(Fastcomp)能够将通过 emcc 生成的 LLVM-IR 代码转换为 ASM.js 及 WebAssembly 代码,而 emsdk 及.emscripten 文件主要是用来帮助我们管理工具链内部的不同版本的子集工具及依赖关系以及相关的用户编译设置。


在我们的日常业务开发过程中,实际上并不需要太过关心 Emscripten 内部的实现细节,Emscripten 已经非常成熟且易于使用。但相关读者若想知道 Emscripten 内部的更多细节,可以访问Emscripten官网以及Github阅读相关WIKI进一步了解。

下载、安装与配置

在进行相关操作之前,请先确保已经安装 git 工具并能够使用基本的 git 命令,接下来我们以 Linux 系统下的操作作为示例演示如何下载、安装及配置 Emscripten。若你的操作系统为 Windows 或是 OSX 等其他系统,请参考官方文档中的相关章节进行操作。


  • 安装


进入你自己的安装目录,执行如下命令获取到 Emscripten SDK Manager(emsdk):


> git clone https://github.com/emscripten-core/emsdk.git
复制代码


  • 下载

  • 进入 emsdk 目录,并执行如下的命令进行安装操作:


> cd emsdk> git pull> ./emsdk install latest
复制代码


需要注意的是,install 命令可以安装特定版本的 Emscripten 开发包及其依赖的所有自己工具,例如:


> ./emsdk install 1.38.45
复制代码


  • 激活及配置

  • 当安装完成后,我们可以通过如下命令进行 Emscripten 的激活和配置:


> ./emsdk activate latest # or ./emsdk activate 1.38.45> source ./emsdk_env.sh
复制代码


现在让我们执行 emcc -v 命令查看相关的信息,若正确输出如下类似信息则说明 Emscripten 安装及配置成功。



emcc -v 的相关信息输出

小试身手

终于进入有趣的部分了,按照惯例,我们先以打印 Hello World! 作为我们学习 WebAssembly 的第一个程序吧!让我们先快速编写一个 C/C++的打印 Hello World! 代码,如下所示:


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


这个程序很简单,使用相关的 GCC 等相关编译器能够很正确得到对应的输出。那么如何产出 WebAssembly 的程序呢?依靠 Emscripten 整个操作也非常简单:


> emcc main.c -o hello.html
复制代码


执行完毕后你将得到三个文件代码,分别是:


  • hello.html

  • hello.js:相关的胶水代码,包括加载 WASM 文件并执行调用等相关逻辑

  • hello.wasm:编译得到的核心 WebAssembly 执行文件


接着我们在当前目录启动一个静态服务器程序(例如 NPM 中的 static-server),然后访问 hello.html 后我们就能看到 Hello World! 在页面上正确输出了!当然,实际上 hello.html 文件并不是一定需要的,如果我们想要让 NodeJS 使用我们代码,那么直接执行:


> emcc main.c
复制代码


即可得到 a.out.jsa.out.wasm 两个文件,然后我们使用 NodeJS 执行:


> node a.out.js
复制代码


也能正确的得到对应的输出(你可以自行创建 html 文件并引入 a.out.js进行浏览器环境的执行 )。


当然,在我们的日常的业务开发中相关程序是不可能如此简单的。除了我们自己的操作逻辑外,我们还会依赖于非常多商用或开源的第三方库及框架。比如在数据通信及交换中我们往往会使用到 JSON 这种轻量的数据格式。在 C/C++中有非常多相关的开源库能解决 JSON 解析的问题,例如cJSON等,那么接下来我们就增加一点点复杂度,结合 cJSON 库编一个简单的 JSON 解析的程序。


首先我们从 Github 中找到 cJSON 的主页,然后下载相关的源码放置在我们项目的 vendor 文件夹中。接着我们在当前项目的根目录下创建一个CMakeList.txt文件,并填入如下内容:


cmake_minimum_required(VERSION 3.15) # 根据你的需求进行修改project(sample C)
set(CMAKE_C_STANDARD 11) # 根据你的C编译器支持情况进行修改set(CMAKE_EXECUTABLE_SUFFIX ".html") # 编译生成.html
include_directories(vendor) # 使得我们能引用第三方库的头文件add_subdirectory(vendor/cJSON)
add_executable(sample main.c)
# 设置Emscripten的编译链接参数,我们等等会讲到一些常用参数
set_target_properties(sample PROPERTIES LINK_FLAGS "-s EXIT_RUNTIME=1")target_link_libraries(sample cjson) # 将第三方库与主程序进行链接
复制代码


那什么是 CMakeList.txt 呢?简单来说,CMakeList.txtCMake 的“配置文件”,CMake 会根据 CMakeList.txt 的内容帮助我们生成跨平台的编译命令。在我们现在及之后的文章中,不会涉及非常复杂的 CMake 的使用,你完全可以把 CMakeList.txt 里的相关内容当成固定配置提供给多个项目的复用,如若需要更深入的了解 CMake 的使用,可以参考 CMake官网教程及文档。好了,现在让我们在代码中引入 cJSON 然后并使用它进行 JSON 的解析操作,代码如下:


#include <stdio.h>#include "cJSON/cJSON.h"
int main() { const char jsonstr[] = "{\"data\":\"Hello World!\"}"; cJSON *json = cJSON_Parse(jsonstr);
const cJSON *data = cJSON_GetObjectItem(json, "data"); printf("%s\n", cJSON_GetStringValue(data));
cJSON_Delete(json); return 0;}
复制代码


代码的整体逻辑非常简单易懂,在这里就不再赘述。由于我们使用了 CMake,因此 Emscripten 的编译命令需要有一点点修改,我们将不使用 emcc 而是使用 emcmake 及 emmake 来创建我们的相关 WebAssembly 代码,命令如下:


> mkdir build> cd build> emcmake cmake ..> emmake make
复制代码


我们创建了一个 build 文件夹用来存放 cmake 相关的生成文件及信息,接着进入 build 文件夹并使用 emcmake 及 emmake 命令生成对应的 WebAssembly 代码 sample.html、sample.js、sample.wasm,最后我们执行访问 sample.html 后可以看到其正确的输出了 JSON 的 data 内容。


如若你从未使用过 CMake,请不要为 CMake 的相关内容因不理解而产生沮丧或者畏难情绪。在我的日常的 WebAssembly 开发中,基本都是沿用一套 CMakeList.txt 并进行增删改,与此同时编译流程基本与上诉内容一致,你完全可以将这些内容复制在你的备忘录里,下次需要用到时直接修改即可。

WASM 的调试

对于开发的 WebAssembly 代码而言,我们对于调试可以使用两种方式,一种方式是通过日志的方式进行输出,另一种方式使用单步调试。使用日志的方式输出调试信息非常容易,Emscripten 能很好的支持 C/C++里面的相关 IO 库。而对于单步调试而言,目前最新版本的 Firefox 及 Chrome 浏览器都已经有了一定的支持,例如我们有如下代码:


#include <stdio.h>
int main() { printf("Hello World!"); return 0;}
复制代码


然后我们使用 emcc 进行编译得到相关的文件:


> emcc -g4 main.c -o main.wasm # -g4可生成对应的sourcemap信息
复制代码


接着打开 Chrome 及其开发者工具,我们就可以看到对应的 main.c 文件并进行单步调试了。



使用 Chrome 进行单步调试


但值得注意的是,目前 emcmake 对于 soucemap 的生成支持并不是很好,并且浏览器的单步调试支持也仅仅支持了代码层面的映射关系,对于比较复杂的应用来说目前的单步调试能力还比较不可用,因此建议开发时还是以日志调试为主要手段。

JavaScript 调用 WASM

对于 WebAssembly 项目而言,我们经常会需要接收外部 JavaScript 传递的相关数据,难免就会涉及到互操作的问题。回到最开始的 JSON 解析例子,我们一般情况而言是需要从外部 JavaScript 中获取到 JSON 字符串,然后在 WebAssembly 代码中进行解析后做对应的业务逻辑处理,并返回对应的结果给外部 JavaScript。接下来,我们会增强 JSON 解析的相关代码,实现如下:


#include <stdio.h>#include "cJSON/cJSON.h"
int json_parse(const char *jsonstr) { cJSON *json = cJSON_Parse(jsonstr); const cJSON *data = cJSON_GetObjectItem(json, "data"); printf("%s\n", cJSON_GetStringValue(data)); cJSON_Delete(json); return 0;}
复制代码


在如上代码中,我们将相关逻辑封装在 json_parse 的函数之中,以便外部 JavaScript 能够顺利的调用得到此方法,接着我们修改一下 CMakeList.txt 的编译链接参数:


#....set_target_properties(sample PROPERTIES LINK_FLAGS "\    -s EXIT_RUNTIME=1 \    -s EXPORTED_FUNCTIONS=\"['_json_parse']\"")
复制代码


EXPORTED_FUNCTIONS 配置用于设置需要暴露的执行函数,其接受一个数组。这里我们需要将 json_parse 进行暴露,因此只需要填写 _json_parse即可。需要注意的是,这里暴露的函数方法名前面以下划线(_)开头。然后我们执行 emcmake 编译即可得到对应的生成文件。


接着我们访问 sample.html,并在控制台执行如下代码完成 JavaScript 到 WebAssembly 的调用:


let jsonstr = JSON.stringify({data:"Hello World!"});jsonstr = intArrayFromString(jsonstr).concat(0);
const ptr = Module._malloc(jsonstr.length);Module.HEAPU8.set(jsonstr, ptr);Module._json_parse(ptr);
复制代码


在这里,intArrayFromStringModule._malloc 以及 Module.HEAPU8 等都是 Emscripten 提供给我们的方法。 intArrayFromString 会将字符串转化成 UTF8 的字符串数组,由于我们知道 C/C++中的字符串是需要 \0 结尾的,因此我们在末尾 concat 了一个 0 作为字符串的结尾符。接着,我们使用 Module._malloc 创建了一块堆内存并使用 Module.HEAPU8.set 方法将字符串数组赋值给这块内存,最后我们调用 _json_parse 函数即可完成 WebAssembly 的调用。


需要注意的是,由于 WebAssembly 端的 C/C++代码接收的是指针,因此你是不能够将 JavaScript 的字符串直接传给 WebAssembly 的。但如果你传递的是 int、float 等基本类型,那么就可以直接进行传递操作。当然,上面的代码我们还可以进一步简化为:


const jsonstr = JSON.stringify({data:"Hello World!"});const ptr = allocate(intArrayFromString(jsonstr), 'i8', ALLOC_NORMAL);Module._json_parse(ptr);
复制代码


那为何需要如此繁琐的方式才能进行引用/指针类型的调用传参呢?在这里我们深入一点 Emscripten 的底层实现,为了方便说明,我们以 ASM.js 的相关逻辑作为参考进行剖析(WASM 实现同理)。我们调整下对应的 CMakeList.txt 将代码编译为 ASM.js:


set_target_properties(sample PROPERTIES LINK_FLAGS " \    -s WASM=0 \    -s TOTAL_MEMORY=16777216 \    -s EXIT_RUNTIME=1 \    -s EXPORTED_FUNCTIONS=\"['_json_parse']\" \")
复制代码


在这里我们将对应的编译链接参数增加 -s WASM=0-s TOTAL_MEMORY=16777216,然后进行相关的编译操作得到 sample.htmlsample.js。首先我们来了解一下 -s TOTAL_MEMORY=16777216 的作用,我们搜索 16777216 这个数字时我们可以看到如下的代码:


function updateGlobalBufferAndViews(buf) {  buffer = buf;  Module['HEAP8'] = HEAP8 = new Int8Array(buf);  Module['HEAP16'] = HEAP16 = new Int16Array(buf);  Module['HEAP32'] = HEAP32 = new Int32Array(buf);  Module['HEAPU8'] = HEAPU8 = new Uint8Array(buf);  Module['HEAPU16'] = HEAPU16 = new Uint16Array(buf);  Module['HEAPU32'] = HEAPU32 = new Uint32Array(buf);  Module['HEAPF32'] = HEAPF32 = new Float32Array(buf);  Module['HEAPF64'] = HEAPF64 = new Float64Array(buf);}
var STATIC_BASE = 8, STACK_BASE = 2960, STACKTOP = STACK_BASE, STACK_MAX = 5245840, DYNAMIC_BASE = 5245840, DYNAMICTOP_PTR = 2928;
// ....
var INITIAL_TOTAL_MEMORY = Module['TOTAL_MEMORY'] || 16777216;
// ....

if (Module['buffer']) { buffer = Module['buffer'];} else { buffer = new ArrayBuffer(INITIAL_TOTAL_MEMORY);}
INITIAL_TOTAL_MEMORY = buffer.byteLength;updateGlobalBufferAndViews(buffer);
复制代码


在这段代码中我们可以看到实际上 Emscripten 帮助我们使用 ArrayBuffer 开辟了一块内存,并将这块内存分为了 栈(STACK)堆(DYNAMIC/HEAP) 两个区域,而这里的 TOTAL_MEMORY 实际上是指明了程序运行内存的实际可用大小(这里非常像简化版的进程内存布局)。同时我们可以看到我们在上面提及的 Module.HEAPU8 等实际上只是这块内存上的不同类型的指针类型(或者说不同的 ArrayBuffer 类型)。因此当我们在进行 Module.HEAPU8.set 的相关操作时,其本质上也是在对这块内存进行相关的操作。


接着我们查找 _json_parse 关键字,_json_parse 的编译后代码如下所示:


function _json_parse($jsonstr) { $jsonstr = $jsonstr|0; // ... sp = STACKTOP; STACKTOP = STACKTOP + 16|0; // ... $jsonstr$addr = $jsonstr; $0 = $jsonstr$addr; $call = (_cJSON_Parse($0)|0); // ... HEAP32[$vararg_buffer>>2] = $call2; (_printf(1005,$vararg_buffer)|0); STACKTOP = sp;return 0;}
复制代码


对于 _json_parse 这个函数调用而言,由于我们传入的是字符串,因此 $jsonstr 实际上是程序运行内存上的某个地址,其很自然地进行了 |0 操作。接着它先对栈顶进行了保存,然后将 $jsonstr$addr(实际上就是 $jsonstr )传递给了 _cJSON_Parse 函数,最后进行一系列相关调用后恢复栈地址,结束运行。在这里需要我们注意的是,实际上 $jsonstr$addr 的相关连续内存的内容上就是我们通过 Module.HEAPU8.set 设置的对应数据,如果需要传递类似如上的指针数据的话,其实质上是传递了程序运行内存的对应地址信息。因此我们如果直接传入 JavaScript 的原生字符串、对象、数组等对象参数,ASM.js 并不能将其从自己程序的运行内存中获取(内存地址信息并不一致)。对于 WebAssembly 而言其调用本质与 ASM.js 一致,若有兴趣可以编译后自行探索。

WASM 调用 JavaScript

WebAssembly 在执行完成之后可能会需要返回部分返回值,针对这个场景其也分为两种情况:


  • 如果返回 int、float、double 等基础类型,那么直接函数声明返回类型后返回即可;

  • 如果需要返回数组、指针等类型,则可以通过 EM_ASM 或是 Memory Copy 的方式进行处理;


例如我们在 WebAssembly 端接收并解析 JSON 字符串后,判断对应数值然后返回修改后的 JSON 字符串,这个需求我们采用 EM_ASM 方式的代码如下:


#include <stdio.h>#include "cJSON/cJSON.h"#ifdef __EMSCRIPTEN__#include <emscripten.h>#endif
int json_parse(const char *jsonstr) { cJSON *json = cJSON_Parse(jsonstr); cJSON *data = cJSON_GetObjectItem(json, "data"); cJSON_SetValuestring(data, "Hi!");
const char *result = cJSON_Print(json); #ifdef __EMSCRIPTEN__ EM_ASM({ if(typeof window.onRspHandler == "function"){ window.onRspHandler(UTF8ToString($0)) } }, result); #endif
cJSON_Delete(json); return 0;}
复制代码


首先我们引入 emscripten.h 头文件,接着我们使用 EM_ASM 调用外部的 window.onRspHandler 回调方法即可完成对应需求。EM_ASM 大括号内可以书写任意的 JavaScript 代码,并且可以对其进行传参操作。在本例中,我们将 result 传递给 EM_ASM 方法,其 $0 为传参的等价替换,若还有更多参数则可以写为 $1$2等。接着,我们编译对应代码,然后访问 sample.html,并在控制台执行如下代码完成 JavaScript 到 WebAssembly 的调用:


window.onRspHandler = (result) => {    console.log(result); // output: {"data":"Hi!"}};
const jsonstr = JSON.stringify({data:"Hello World!"});const ptr = allocate(intArrayFromString(jsonstr), 'i8', ALLOC_NORMAL);Module._json_parse(ptr);
复制代码


可以看到,window.onRspHandler 函数被调用并正确的进行了结果输出。实际上 Emscripten 给我们提供了非常多的 JavaScript 调用函数及宏,包括:


  • EM_ASM

  • EM_ASM_INT

  • emscripten_run_script

  • emscripten_run_script_int

  • emscripten_run_script_string

  • emscripten_async_run_script


但是在一般实践中我们推荐使用 EM_ASM_* 的相关宏来进行对应的 JavaScript 调用,其原因在于 EM_ASM_* 的内容在编译中会被抽出内联为对应的 JavaScript 函数,上面的例子在编译之后实际上得到的内容如下所示:


function _json_parse($jsonstr) {  // ...  $call4 = _emscripten_asm_const_ii(0,($4|0))|0;  // ...}
复制代码


我们可以看到在这里,我们 EM_ASM 的调用其实质是直接调用了 _emscripten_asm_const_ii,而 _emscripten_asm_const_ii 函数内容如下:


var ASM_CONSTS = [function($0) {     if(typeof window.onRspHandler == "function"){         window.onRspHandler(UTF8ToString($0))     } }];
function _emscripten_asm_const_ii(code, a0) { return ASM_CONSTS[](a0);}
复制代码


我们所编写的 JavaScript 代码被放置到了 ASM_CONSTS 数组之中,然后被通过对应的索引位置进行调用。而对于 emscripten_run_script_* 相关函数而言,其实质是调用了 eval 来进行执行。因此两者在频繁调用的场景下会有比较大的性能差距。分析完 EM_ASM 的方式,那如果我们使用 Memory Copy 的话怎么做呢?代码如下:


#include <stdio.h>#include <memory.h>#include <string.h>#include "cJSON/cJSON.h"
int json_parse(const char *jsonstr, char *output) { cJSON *json = cJSON_Parse(jsonstr); cJSON *data = cJSON_GetObjectItem(json, "data"); cJSON_SetValuestring(data, "Hi!");
const char *string = cJSON_Print(json); memcpy(output, string, strlen(string));
cJSON_Delete(json); return 0;}
复制代码


我们相比之前的实现多传递了一个参数 output,在 WebAssembly 端解析、改写 JSON 完成后,使用 memcpy 将对应结果复制到 output 当中。接着,我们编译对应代码,然后访问 sample.html,并在控制台执行如下代码完成 JavaScript 到 WebAssembly 的调用:


const jsonstr = JSON.stringify({data:"Hello World!"});const ptr = allocate(intArrayFromString(jsonstr), 'i8', ALLOC_NORMAL);
const output = Module._malloc(1024);Module._json_parse(ptr, output);console.log(UTF8ToString(output)); // output: {"data":"Hi!"}
复制代码


如上所示,我们使用 Malloc._malloc 创建了一块堆内存,并传递给 _json_parse 函数,同时使用 UTF8ToString 方法将对应 JSON 字符串结果输出。

使用更多的 Emscripten 的 API

实际上 Emscripten 为了方便我们在 C/C++中编写代码,其提供了非常多的 API 供我们使用,其中包括:Fetch、File System、VR、HTML5、WebSocket 等诸多实现。例如我们以 Fetch 为例:


#include <stdio.h>#include <string.h>
#ifdef __EMSCRIPTEN__#include <emscripten/fetch.h>void downloadSucceeded(emscripten_fetch_t *fetch) { printf("%llu %s.\n", fetch->numBytes, fetch->url); emscripten_fetch_close(fetch);}
void downloadFailed(emscripten_fetch_t *fetch) { emscripten_fetch_close(fetch);}#endif
int main() {#ifdef __EMSCRIPTEN__ emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); strcpy(attr.requestMethod, "GET"); attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; attr.onsuccess = downloadSucceeded; attr.onerror = downloadFailed; emscripten_fetch(&attr, "http://myip.ipip.net/");#endif}
复制代码


在上面的代码中我们使用了 emscripten_fetch 相关函数来进行浏览器宿主环境 fetch 方法的调用。为了启用 Emscripten 中的 Fetch 能力,我们还需要修改编译链接参数,为其增加-s FETCH=1:


#....set_target_properties(sample PROPERTIES LINK_FLAGS "\    -s NO_EXIT_RUNTIME=1 \    -s FETCH=1 \")
复制代码


想要了解更多的可用 API 及细节,你可以访问 Emscripten 官网阅读API Reference相关章节

编译链接参数

在上面实践中我们使用了一些编译连接的参数,包括:


  • -g

  • -s EXIT_RUNTIME

  • -s EXPORTED_FUNCTIONS

  • -s FETCH

  • -s NO_EXIT_RUNTIME


实际上,Emscripten 包含了非常丰富的相关设置参数帮助我们在编译和链接时优化我们的代码。其中部分常用的参数包括:


  • -O1、-O2、-O3、-Oz、-Os、-g 等:编译优化,具体可参考 Emscripten 官网相关章节;

  • -s ENVIRONMENT:设定编译代码的可执行环境,默认值为"web,work,node";

  • -s SINGLE_FILE:是否将 ASM.js 或 WebAssembly 代码以 Base64 的方式嵌入到 JavaScript 胶水代码中,可取值 0/1;

  • -s WASM:是否编译为 WebAssembly 代码,0 编译为 ASM.js,1 编译为 WebAssembly;

  • -s FETCH:是否启用 Fetch 模块,可取值 0/1;

  • -s DISABLE_EXCEPTION_CATCHING:禁止生成异常捕获代码,可取值 0/1;

  • -s ERROR_ON_UNDEFINED_SYMBOLS:编译时出现 Undefined Symbols 后是否退出,可取值 0/1;

  • -s EXIT_RUNTIME: 执行完毕 main 函数后是否退出,可取值 0/1;

  • -s FILESYSTEM:是否启用 File System 模块,可取值 0/1;

  • -s INVOKE_RUN:是否执行 C/C++的main函数,可取值 0/1;

  • -s ASSERTIONS:是否给运行时增加断言,可取值 0/1;

  • -s TOTAL_MEMORY:总的可用内存使用数,可取以 16777216 为基数的整数值;

  • -s ALLOW_MEMORY_GROWTH:当可用内存不足时,是否自动增长,可取值 0/1;

  • -s EXPORTED_FUNCTIONS:暴露的函数列表名称;

  • -s LEGACY_VM_SUPPORT:是否增加部分兼容函数以兼容低版本浏览器(iOS9、老版本 Chrome 等),可取值 0/1;

  • -s MEM_INIT_METHOD:是否将.mem 文件以 Base64 的方式嵌入到 JavaScript 胶水代码中,可取值 0/1;

  • -s ELIMINATE_DUPLICATE_FUNCTIONS:将重复函数进行自动剔除,可取值 0/1;

  • –closure: 是否使用 Google Closure 进行最终代码的压缩,可取值 0/1;

  • –llvm-lto:是否进行 LLVM 的链接时优化,可取值 0-3;

  • –memory-init-file:同-s MEM_INIT_METHOD;


更多编译链接参数设置可以参考 emsdk/src/settings.js 文件。

总结

在本章中我们较为详细地介绍了 Emscripten 的入门使用,关于 Emscripten 的更多内容(代码性能及体积优化、API 使用等)可以参考Emscripten官网Github的WIKI。在接下来的文章中,我们会以具体需求实例为入口,帮助大家能够更好地学习 Emscripten 在实际生产中的使用。


推荐阅读:


WebAssembly 如何演进成为“浏览器第二编程语言”?


2020-09-09 14:0011316

评论 1 条评论

发布
用户头像
补充一下:如果链接时出现了链接异常: undefined symbol: __stack_chk_guard

需要修改一下cJson中的CMakeLists,-fstack-protector-strong 选项去掉即可 。

另外补充一下__stack_chk_guard在libssp库中

fetch

2022-10-11 19:11 · 广东
回复
没有更多了
发现更多内容

DY短视频批量爬虫提取工具功能介绍

Geek_16d138

好用的软件分享

ZOC8 for Mac v8.08.2激活版:顶级终端仿真器

iMac小白

ZOC8 下载 ZOC8 中文版 ZOC8 激活版 ZOC8 破解版 ZOC8 mac

理解标准数据类型及类型查看

测吧(北京)科技有限公司

测试

重磅更新:草料二维码容量调整,不再限制图片文件上传量

草料二维码

Bonree ONE 2024春季正式版:革新智能运维,7 大功能亮点重磅发布

博睿数据

探索Git分支管理:优化团队协作与项目开发

测吧(北京)科技有限公司

测试

探究字符串操作的各种类别

测吧(北京)科技有限公司

测试

线程池核心原理浅析

不在线第一只蜗牛

线程 核心原理

为什么公共云的弹性能力很难被发挥出来?

AutoMQ

Java 大数据 云原生 AutoMQ

解锁工业数据流:NeuronEX 规则调试功能实操指南

EMQ映云科技

emq emqx NeuronEX

理论+实践,带你了解分布式训练

快乐非自愿限量之名

分布式 分布式训练 语言模型

关于零信任理念, 零信任的重点是什么

德迅云安全杨德俊

企业如何训练自己的专属大模型?

幂简集成

AI API 大模型

基于OceanBase+Flink CDC,云粒智慧实时数仓演进之路

Apache Flink

flink oceanbase 实时数据

建发弘爱 X 袋鼠云:加速提升精细化、数字化医疗健康服务能力

袋鼠云数栈

数据资产 医疗 医疗AI 医疗中台 数据服务

2024春季火山引擎FORCE原动力大会,5月15日开幕

新消费日报

一文搞懂 ARM 64 系列: ADC

EquatorCoco

开发 arm

云手机:海外舆情监控的新工具

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机 跨境云手机

深入理解变量:定义、使用和地址

测吧(北京)科技有限公司

测试

SD-WAN助力企业实现多分支互联

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

深入理解逻辑运算符及其短路特性

测吧(北京)科技有限公司

测试

释放效率:IDE Git集成与代码管理技艺

测吧(北京)科技有限公司

测试

深入了解字符串:定义、转义字符和字符串下标

测吧(北京)科技有限公司

测试

元组与列表:相同点、不同点及内存占用

测吧(北京)科技有限公司

测试

深入理解计数器函数:闭包与装饰器

测吧(北京)科技有限公司

测试

深入了解条件判断、状态标记和假值状态

测吧(北京)科技有限公司

测试

什么是运算符

测吧(北京)科技有限公司

测试

了解元组:定义、特点、应用及常用方法

测吧(北京)科技有限公司

测试

KaiwuDB 参编的《分析型数据库技术要求》标准正式发布

KaiwuDB

数据库 分析型数据库 数据库行业标准制定 数据库标准

从0开始快速上手WebAssembly:Emscripten使用入门_语言 & 开发_赵洋_InfoQ精选文章