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默认情况下会发出它),但有时您可能需要您的代码在尚未支持WebAssembly的环境中运行,因此应禁用它。

提示

如果在调用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文件或打包的文件数据,如下所述)。对于这些浏览器,您需要使用本地Web服务器提供文件服务,然后打开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模拟了一个文件系统,您可以使用正常的libc stdio API从已编译的C/C++代码中访问该文件系统。

您要访问的文件应该被预加载嵌入到虚拟文件系统中。预加载(或嵌入)将生成一个虚拟文件系统,该文件系统对应于编译相对于当前目录的文件系统结构。

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 Workers中),而许多本机代码使用同步文件系统访问。预加载确保在已编译的代码有机会访问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 的其他一般技巧