快速上手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_CONSTScode;
}

我們所編寫的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 如何演進成爲“瀏覽器第二編程語言”?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章