Emscripten 入门 – 将 C/C++ 转译为 JavaScript / HTML5





5.00/5 (4投票s)
本文将重点介绍如何创建自己的 Web 项目,充分利用 Emscripten 的功能,从而让你能够将 C/C++ 代码在浏览器中运行。
为多个平台编写代码可能非常耗时。如果需要为每个平台完全重写一遍,那将更加耗时。如果你用 C++ 编写了一个应用程序,但想在浏览器中显示它,该怎么办?现在,有了 Emscripten 这个工具,这一切都成为可能。
Emscripten 是一个基于 LLVM 的项目,它将 C 和 C++ 编译成高度优化的 JavaScript(asm.js 格式)。简而言之:使用 C 和 C++,在浏览器中获得接近原生速度的性能。更棒的是,Emscripten 将桌面图形 API OpenGL 转换为 WebGL,这是该 API 的 Web 版本。
我之前写过一篇博文,说明了 Emscripten 是什么,以及它如何与我最喜欢的游戏开发工具:Unreal Engine 4 和 Unity 相关联。因此,我在这里不会详细介绍它的工作原理,而是想重点介绍如何创建自己的 Web 项目,充分利用 Emscripten 的功能,让你能够将 C/C++ 代码在浏览器中运行。
平台
我使用的是 Windows 10,但对于类 Unix 平台、OS X 和 Linux,过程是相同的。此时唯一需要进行的实际更改是输入代码到命令行/终端的方式。在 Windows 上,我只需输入 emcc。在类 Unix 环境中,你必须在命令前加上 ./,所以你的命令会是:./emcc。
安装过程
我最初曾考虑不写这篇教程,但在安装过程中遇到了许多问题,我认为这将对他人有所帮助。我查看了 Epic Games 推荐的从 UE4 导出项目的方法,他们 建议使用 SDK Web 安装程序。 这让我陷入了困境。我曾希望安装过程会很顺利,但在成功之前,我在让 Emscripten 在我的机器上正常工作方面花费了近两天时间。该团队在官网上提供了非常棒的 入门指南,但如果你选择了错误的安装程序,可能会遇到一些麻烦。
我的第一个问题是我选择了 Web 安装程序。这不是你要找的安装程序。 相反,请使用SDK 离线安装程序,这样你就会一路顺畅,并且只需要几分钟时间。
Web 安装程序存在许多 bug,尤其是在你安装的各种 Visual Studio 版本之间。这是一个已知问题,但正在得到纠正。 我认为这与 Emscripten 在后台进行的并行优化有关,我发现 Cmake 指向了我安装的最新版本 Visual Studio(2015 Community),而不是 Emscripten 所需的 VS 2010。
你可以在 Visual Studio 中使用 Emscripten,但我发现从 Emscripten CLI 进行构建更容易。如果你确实想使用 Visual Studio,说明在这里,尽管目前只支持 VS 2010 和 2013。
“Emscripten SDK 以一个易于安装的包提供了整个 Emscripten 工具链(Clang、Python、Node.js 和 Visual Studio 集成),并支持更新到更新的 SDK。”
–Emscripten 入门指南
入门
运行Emscripten 命令提示符即可开始。我通常按下 win + ems 即可找到它。
你将看到 CLI 界面,并且会从 Emscripten 的安装位置开始。
导航到你的emsdk 根目录。我通过更改目录到 emscripten\tag-1.34.1 来实现这一点,因为你可以看到上面图片中设置的 emscripten 路径是
EMSCRIPTEN = C:\Program Files\Emscripten\emscripten\tag-1.341
提示: 随时可以在 CLI 中输入 'explorer .' 来打开资源管理器文件夹,更好地可视化你正在查看的内容。对于包含几十个项目的文件夹,这通常比输入 'ls' 列出目录中的所有内容更容易。
运行 'emcc -v' 是你的“ sanity check”,以验证你正在运行的版本中的所有内容都正常工作。你应该会看到类似如下的内容
既然你已经可以将 C/C++ 代码编译成 JavaScript,让我们来运行第一个示例,直接来自 Emscripten 文档。我们以 hello_world.c 为例,将其转换为 JavaScript。C 代码如下所示
#include<stdio.h>
int main() {
printf("hello, world!\n");
return 0;
}
我们将从 tests 文件夹运行它,该文件夹比当前目录低一级。要构建代码,请输入
emcc tests/hello_world.c
如果一切顺利,你的 CLI 应该会暂停片刻,然后返回到相同的位置。这里发生了什么?编译器并没有很详细地提供任何反馈,所以我们现在添加它,以便更好地理解后台发生的事情。
我们将 '-v' 参数添加到命令中,让编译器输出更详细的信息。
emcc tests/hello_world.c -v
这样就好多了
这只是将我们的应用程序编译成了 C,并在当前目录中创建了一个 a.out.js 文件。如果你更喜欢使用 C++,可以使用 'em++' 而不是 'emcc'。
这个 a.out.js 是关键,因为它包含了基于 asm.js 规范的 JavaScript 代码。如果我们用 node 执行它,你将在 CLI 中看到 'hello, world'。请输入此命令
node a.out.js
这很棒,但我希望在浏览器中看到它。
生成 HTML
首先,检查你的 Web 平台状态,了解支持 asm.js 的浏览器。实际上,我们可以使用 Emscripten 的 –o 参数生成几种不同类型的扩展。根据文档
-o <target>
目标文件名扩展定义了要生成的输出类型
<name> .js:JavaScript。
<name> .html:HTML + 独立的 JavaScript 文件(<name>.js)。拥有独立的 JavaScript 文件可以提高页面加载时间。
<name> .bc:LLVM 位码(默认)。
<name> .o:LLVM 位码(与 .bc 相同)。
在这种情况下,我们需要 HTML 才能在浏览器中查看。请输入此命令来指定一个 HTML 文档
Emcc tests/hello_world./c -o hello.html
我们告诉 Emscripten 使用 C 编译器在 tests 文件夹中查找 hello_world.c 文件,然后将其输出为 hello.html。在你位于 C:\Program Files \Emscripten\emscripten\tag-1.34.1 文件夹中,你应该会看到 hello.html。
用浏览器打开它,你就可以看到它作为一个网页了
但我们再进一步。我想从文件读取并提供一些内容。
从文件读取
由于浏览器在沙箱环境中运行 JavaScript,我们无法直接访问本地文件系统。为了解决这个问题,Emscripten 创建了一个模拟文件系统,以便我们可以使用libc stdio API 来访问编译后的 C/C++ 代码。诀窍在于,虚拟文件系统要求这些文件嵌入或预加载。
在教程的最后一部分,我们转向 hello_world_file.cpp 并从中读取文件,因为这将涵盖我想要讨论的几个部分,包括如何提供内容。
该文件的代码如下所示
#include <stdio.h>
int main() {
FILE *file = fopen("tests/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;
}
如果你有 Web 开发背景,并且不习惯使用强类型语言,你可能在想:“feof 是什么意思?” 这也让我陷入了困境,但这个 Stack Overflow 问题为我解开了不少困惑。
“现在我们来处理 EOF。EOF 是您从尝试的 I/O 操作中获得的响应。它表示您正在尝试读取或写入某些内容,但在执行此操作时,未能读取或写入任何数据,而是遇到了输入或输出的末尾。对于几乎所有的 I/O API 来说,无论是 C 标准库、C++ iostreams 还是其他库,都是如此。只要 I/O 操作成功,您就无法知道后续的、未来的操作是否会成功。您必须始终先尝试操作,然后对成功或失败做出响应。”
这将尝试从位于 tests/hello_world_file.txt 的文件中读取。要做到这一点,我们需要运行此命令
Emcc tests/hello_world_file.cpp -o hello.html –preload-file tests/hello_world_file.txt
现在再次尝试打开同一个 hello.html 文件,看看会发生什么。
在 FireFox 中,一切正常
但在 Chrome 和 Edge 中,我们没有那么幸运。
这是因为这些浏览器不支持 file:// XHR 请求,因此无法加载带有预加载数据的本地文件。我们需要启动一个 Web 服务器。我们已经安装了 Python,因为它是 emscripten 包的一部分,所以让我们从当前目录启动一个简单的服务器,然后重试。
python -m SimpleHTTPServer 8080
现在在 Edge 或 Chrome 中,让我们再次导航到此文件
好了!在浏览器中提供 HTML,而这之前只是 C++。
连接 C++ 和 JavaScript
我知道你在想什么。“这很棒,但如果我的 C++ 和 JavaScript 之间没有任何互操作性,那它就没什么用了。” 幸运的是,它有!
有几种方法
- 从普通的 JavaScript 调用编译后的C函数
- 使用 ccall 或 cwrap
- 使用直接函数调用(速度更快但更复杂)。
- 使用由以下工具创建的绑定,从 JavaScript 调用编译后的C++类
- 从C/C++调用 JavaScript 函数
这超出了本教程的范围,但我将在未来的教程中介绍。在此之前,请查阅 Emscripten 关于此主题的文档。
并非没有限制
Emscripten 在几个方面仍然存在局限性。例如,不能使用多线程代码,因为 JavaScript 是单线程的。我能想到的最好的解决方法是某种方式将代码拆分到Web Workers 中,这是 JavaScript 模拟多线程的方式。
还有一些代码已知可以编译,但在运行时存在性能问题。特别是,64 位整数变量。数学运算速度较慢,因为它们是模拟的,但SIMD(单指令多数据)的引入,随着浏览器对其的采用,在不久的将来会有所帮助。
后续
我今天不想深入研究,但在我下一篇系列教程中,我想介绍一些更复杂的示例,这些示例将利用 SDLib 以及像 Unreal Engine 4 或 Unity 这样的中间件工具,以便我们能够看到 asm.js 在浏览器中的实际性能。
更多 Web 开发实践
本文是 Microsoft 技术传道士和工程师关于实用 JavaScript 学习、开源项目和互操作性最佳实践(包括Microsoft Edge 浏览器和新的EdgeHTML 渲染引擎)的 Web 开发系列的一部分。
我们鼓励你在 Windows 10 的默认浏览器 Microsoft Edge 等浏览器和设备上进行跨浏览器和跨设备测试,可在 dev.microsoftedge.com 上使用免费工具进行测试。
- 扫描您的网站是否存在过时库、布局问题和可访问性问题
- 下载适用于 Mac、Linux 和 Windows 的免费虚拟机
- 查看包括 Microsoft Edge 路线图在内的各浏览器 Web 平台状态
- 在您自己的设备上远程测试 Microsoft Edge
我们工程师和布道者的更深入的学习
- GitHub 上的编程实验室:跨浏览器测试和最佳实践
- Microsoft Edge Web Summit 2015(来自我们的工程团队和 JS 社区)
- 哇,我可以在 Mac 和 Linux 上测试 Edge 和 IE!(来自 Rey Bango)
- 在不破坏 Web 的情况下推进 JavaScript(来自 Christian Heilmann)
- 让 Web 正常工作的 Microsoft Edge 渲染引擎(来自 Jacob Rossi)
- 利用 WebGL 和 Microsoft Edge 释放 3D 渲染(来自 David Catuhe)
- 托管 Web 应用和 Web 平台创新(来自 Kevin Hill 和 Kiril Seksenov)
我们的社区开源项目
- vorlon.JS(跨设备远程 JavaScript 测试)
- manifoldJS(部署跨平台托管 Web 应用)
- babylonJS(轻松进行 3D 图形处理)
更多免费工具和后端 Web 开发内容