Emscripten 教學

在基礎層面上,使用 Emscripten 相當簡單。本教學將引導您完成從命令列編譯您的第一個 Emscripten 範例所需的步驟。它也示範如何處理檔案並設定主要的編譯器最佳化旗標。

首先,先確認一下

請確定您已下載並安裝 Emscripten (確切的方法會取決於您的作業系統:Linux、Windows 或 Mac)。

使用 Emscripten 編譯器前端 (emcc) 存取 Emscripten。此腳本會調用建置程式碼所需的所有其他工具,並且可以作為標準編譯器 (如 gccclang) 的直接替代品。它在命令列上使用 ./emcc./em++ 呼叫。

注意

在 Windows 上,該工具使用稍微不同的語法呼叫:emccem++。本教學的其餘部分使用 Linux 方法 (./emcc)。

對於下一個章節,您需要開啟命令提示字元

  • 在 Linux 或 macOS 上,開啟「終端機」。

  • 在 Windows 上,開啟 Emscripten 命令提示字元,這是一個已預先設定好正確系統路徑和設定的命令提示字元,以指向作用中的 Emscripten 工具。若要存取此提示,請在 Windows 8 開始畫面中輸入 Emscripten,然後選取「Emscripten 命令提示字元」選項。

使用命令提示字元導覽至 SDK 下的 emscripten 目錄。這是emsdk 根目錄下方的資料夾,通常是 <emsdk 根目錄>/upstream/emscripten/。以下範例將取決於尋找相對於該位置的檔案。

注意

在較舊的 emscripten 版本中,目錄結構有所不同:會顯示版本號碼,而且不會顯示後端 (fastcomp/upstream),因此您會使用類似 <emsdk 根目錄>/emscripten/1.20.0/ 的路徑。

驗證 Emscripten

如果您之前未執行過 Emscripten,請立即使用以下指令執行:

./emcc -v

如果輸出包含有關缺少工具的警告,請參閱 驗證 Emscripten 開發環境 以取得偵錯協助。否則,請繼續下一節,我們將在其中建置一些程式碼。

執行 Emscripten

您現在可以將您的第一個 C/C++ 檔案編譯為 JavaScript。

首先,讓我們看看要編譯的檔案:hello_world.c。這是 SDK 中最簡單的測試程式碼,正如您所看到的,它所做的只是在主控台中列印「hello, world!」,然後結束。

/*
 * Copyright 2011 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */

#include <stdio.h>

int main() {
  printf("hello, world!\n");
  return 0;
}

若要建置此程式碼的 JavaScript 版本,只需在 emcc 後面指定 C/C++ 檔案 (使用 em++ 強制編譯為 C++)

./emcc test/hello_world.c

您應該會看到該命令產生了兩個檔案:a.out.jsa.out.wasm。第二個是包含已編譯程式碼的 WebAssembly 檔案,第一個是包含用於載入和執行程式碼的執行階段支援的 JavaScript 檔案。您可以使用 node.js 執行它們

node a.out.js

這會將「hello, world!」列印到主控台中,如預期的那樣。

注意

較舊的 node.js 版本尚未支援 WebAssembly。在這種情況下,您會看到一則錯誤訊息,建議您使用 -sWASM=0 建置以停用 WebAssembly,然後 emscripten 會將編譯後的程式碼發出為 JavaScript。一般而言,建議使用 WebAssembly,因為它具有廣泛的瀏覽器支援,而且執行和下載效率更高 (因此 emscripten 預設會發出它),但有時您可能需要您的程式碼在尚未提供的環境中執行,因此應將其停用。

提示

如果在呼叫 emcc 時發生錯誤,請使用 -v 選項執行它,以列印出大量有用的偵錯資訊。

注意

在本節以及稍後,我們會從 test/ 資料夾執行一些檔案。該資料夾包含 Emscripten 測試套件的檔案。有些可以單獨執行,但其他必須透過測試架構本身執行,請參閱 Emscripten 測試套件 以取得更多資訊。

產生 HTML

Emscripten 也可以產生 HTML 以測試內嵌的 JavaScript。若要產生 HTML,請使用 -o (輸出) 命令,並將 html 檔案指定為目標檔案

./emcc test/hello_world.c -o hello.html

您現在可以在網頁瀏覽器中開啟 hello.html

注意

不幸的是,有幾個瀏覽器 (包括 ChromeSafariInternet Explorer) 不支援 file:// XHR 請求,而且無法載入 HTML 所需的額外檔案 (例如 .wasm 檔案,或如下所述的封裝檔案資料)。對於這些瀏覽器,您需要使用本機網頁伺服器提供檔案,然後開啟 https://127.0.0.1:8000/hello.html)。

將 HTML 載入到瀏覽器後,您會看到一個文字區域,用於顯示原生程式碼中 printf() 呼叫的輸出。

HTML 輸出不僅限於顯示文字。您也可以使用 SDL API 在 <canvas> 元素中顯示彩色立方體 (在支援它的瀏覽器上)。如需範例,請建置 hello_world_sdl.c 測試程式碼,然後重新整理瀏覽器

./emcc test/hello_world_sdl.c -o hello.html

第二個範例的原始程式碼如下所示

// Copyright 2011 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <stdio.h>
#include <SDL/SDL.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

int main(int argc, char** argv) {
  printf("hello, world!\n");

  SDL_Init(SDL_INIT_VIDEO);
  SDL_Surface *screen = SDL_SetVideoMode(256, 256, 32, SDL_SWSURFACE);

#ifdef TEST_SDL_LOCK_OPTS
  EM_ASM("SDL.defaults.copyOnLock = false; SDL.defaults.discardOnLock = true; SDL.defaults.opaqueFrontBuffer = false;");
#endif

  if (SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);
  for (int i = 0; i < 256; i++) {
    for (int j = 0; j < 256; j++) {
#ifdef TEST_SDL_LOCK_OPTS
      // Alpha behaves like in the browser, so write proper opaque pixels.
      int alpha = 255;
#else
      // To emulate native behavior with blitting to screen, alpha component is ignored. Test that it is so by outputting
      // data (and testing that it does get discarded)
      int alpha = (i+j) % 255;
#endif
      *((Uint32*)screen->pixels + i * 256 + j) = SDL_MapRGBA(screen->format, i, j, 255-i, alpha);
    }
  }
  if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
  SDL_Flip(screen);

  printf("you should see a smoothly-colored square - no sharp lines but the square borders!\n");
  printf("and here is some text that should be HTML-friendly: amp: |&| double-quote: |\"| quote: |'| less-than, greater-than, html-like tags: |<cheez></cheez>|\nanother line.\n");

  SDL_Quit();

  return 0;
}

使用檔案

注意

您的 C/C++ 程式碼可以使用一般的 libc stdio API (fopenfclose 等) 存取檔案。

JavaScript 通常會在網頁瀏覽器的沙箱環境中執行,無法直接存取本機檔案系統。Emscripten 會模擬您可以從編譯後的 C/C++ 程式碼使用一般的 libc stdio API 存取的檔案系統。

您要存取的檔案應該預先載入內嵌到虛擬檔案系統中。預先載入 (或內嵌) 會產生與編譯時的檔案系統結構相對應的虛擬檔案系統,相對於目前目錄

hello_world_file.cpp 範例示範如何載入檔案 (測試程式碼和要載入的檔案如下所示)

// Copyright 2012 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <stdio.h>

int main() {
  FILE *file = fopen("test/hello_world_file.txt", "rb");
  if (!file) {
    printf("cannot open file\n");
    return 1;
  }
  while (!feof(file)) {
    char c = fgetc(file);
    if (c != EOF) {
      putchar(c);
    }
  }
  fclose (file);
  return 0;
}
==
This data has been read from a file.
The file is readable as if it were at the same location in the filesystem, including directories, as in the local filesystem where you compiled the source.
==

注意

該範例預期能夠載入位於 test/hello_world_file.txt 的檔案

FILE *file = fopen("test/hello_world_file.txt", "rb");

我們會從 test「上方」的目錄編譯範例,以確保建立的虛擬檔案系統具有與編譯時目錄相對應的正確結構。

以下命令用於指定要預先載入到 Emscripten 虛擬檔案系統中的資料檔案 — 在執行任何編譯程式碼之前。這種方法很有用,因為瀏覽器只能從網路非同步載入資料 (Web Worker 除外),而許多原生程式碼使用同步檔案系統存取。預先載入可確保在編譯程式碼有機會存取 Emscripten 檔案系統之前,資料檔案的非同步下載已完成 (並且檔案可用)。

./emcc test/hello_world_file.cpp -o hello.html --preload-file test/hello_world_file.txt

執行上述命令後,在網頁瀏覽器中開啟 hello.html,即可看到來自 hello_world_file.txt 的資料顯示。

如需更多關於檔案系統操作的資訊,請參閱檔案系統總覽檔案系統 API同步虛擬 XHR 後端檔案系統使用方式

程式碼最佳化

Emscripten 預設會像 gccclang 一樣產生未最佳化的程式碼。您可以使用 -O1 命令列參數來產生稍微最佳化的程式碼。

./emcc -O1 test/hello_world.cpp

a.out.js 中建立的「hello world」程式碼實際上並不需要最佳化,因此與未最佳化版本相比,您不會看到速度上的差異。

然而,您可以比較產生的程式碼來查看差異。-O1 會應用一些小型的最佳化並移除一些執行時的斷言。例如,產生的程式碼中,printf 會被替換為 puts

-O2 提供的最佳化(請參閱此處)更具侵略性。如果您執行以下命令並檢查產生的程式碼(a.out.js),您會發現它看起來非常不同。

./emcc -O2 test/hello_world.cpp

如需更多關於編譯器最佳化選項的資訊,請參閱程式碼最佳化emcc 工具參考

Emscripten 測試套件和基準測試

Emscripten 有一個全面的測試套件,涵蓋了幾乎所有 Emscripten 的功能。這些測試對開發人員來說是極好的資源,因為它們提供了大多數功能的實用範例,並且已知可以在 main 分支上成功建置。

請參閱 Emscripten 測試套件 以取得更多資訊。

一般提示和下一步驟

本教學引導您完成從命令列呼叫 Emscripten 的第一個步驟。當然,這個工具還有更多可以做的事情。以下是使用 Emscripten 的其他一般提示:

  • 本網站有更多關於編譯和建置專案將您的原生程式碼與網路環境整合打包您的程式碼和發佈的資訊。

  • Emscripten 測試套件是尋找如何使用 Emscripten 範例的好地方。例如,如果您想更好地了解 emcc --pre-js 選項的工作方式,請在測試套件中搜尋 --pre-js:測試套件非常廣泛,很可能至少有一些範例。

  • 若要了解如何以進階方式使用 Emscripten,請閱讀src/settings.jsemcc,其中描述了編譯器選項,以及 emscripten.h,其中詳細說明了當使用 Emscripten 編譯時,您的 C/C++ 程式可以使用的 JavaScript 特定 C API。

  • 請閱讀常見問題

  • 如有疑問,請聯繫我們