将你的 C++ 代码带到 Web






4.77/5 (10投票s)
如何将您的 C++ 代码带到 Web
目录
- 引言
- 安装适用于 Linux 的 Windows 子系统
- 安装 Emscripten
- Hello World 程序
- 从 C/C++ 调用 JavaScript 代码
- 从 JavaScript 调用 C/C++ 代码
- 跨平台
引言
WebAssembly 的前身 asm.js 将 C/C++、Rust 代码转换为低级 JavaScript,以便在 Web 浏览器上运行。WebAssembly 于 2017 年 3 月推出,它使代码(无论是原生代码还是托管代码)都能编译成基于堆栈的虚拟机二进制指令格式,从而在 JavaScript 的性能上实现了 20% 到 600% 的提升,这是十年前无法想象的壮举。今天的文章将提供分步说明,介绍如何安装 Emscripten 工具集,并在 20 分钟内让“Hello World
”程序在我们选择的 Web 浏览器上运行。读者的实际操作时间可能会因其互联网速度/质量而异。
安装适用于 Linux 的 Windows 子系统
为了使用 Emscripten,我们需要安装适用于 Linux 的 Windows 子系统 (WSL),这是 Windows 10 上的一项功能,因此不支持旧版本的 Windows。如果您是 apt Linux 用户,欢迎使用您喜欢的 Linux 发行版。如果您不想安装 Linux 操作系统,也可以下载 Emscripten Windows 安装程序。这些 Windows 安装程序在我几年前使用时就没起作用,但情况可能已经有所改善。在尝试 Linux 路线之前,尝试 Windows 安装程序没有坏处。最新的 Windows 安装程序通常比最新版本落后几个版本。如果您不是那些紧跟技术前沿的人,这也许是可以的。
为什么选择 WSL?
其他虚拟机选项包括 Oracle 的 VirtualBox 和 Microsoft 的 Hyper-V。VirtualBox 只支持 32 位客户机操作系统。Emscripten 工具需要 4GB 以上的 RAM 才能构建,更准确地说,是为了链接而不是编译。超过 4GB 的 RAM 意味着 64 位操作系统,因此排除了 VirtualBox。Hyper-V 支持 64 位客户机,但仅随 Windows 10 Pro 提供。家庭用户通常拥有 Windows 家庭版。在我们的情况下,WSL 是最吸引人的选择。
在 Windows 10 上启用 WSL
在我们可以从 Microsoft 安装 Ubuntu 之前,我们必须先启用开发人员模式和 WSL。要启用开发人员模式,请转到“设置”>“更新和安全”>“开发者选项”,然后选择“开发人员模式”。
也可以通过 PowerShell 完成此操作。以管理员模式启动 PowerShell。然后键入以下命令并按“Enter”键。
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
接下来,必须在“Windows 功能”中启用 WSL。在 Windows 搜索栏中,键入“打开或关闭 Windows 功能”并选择该选项。一直向下滚动并选中“适用于 Linux 的 Windows 子系统”选项,如图所示。
安装 Ubuntu 18.04
接下来,通过单击任务栏上的按钮启动 Microsoft Store。搜索“Ubuntu”,然后在搜索结果中单击 Ubuntu 18.04 上的“安装”。这大约需要 5 分钟。
Ubuntu 首次启动重要提示
Ubuntu 安装完成后,Windows 会要求您启动它。停止!不要按“启动”按钮!您必须通过右键单击 Ubuntu 并选择“以管理员身份运行”来以管理员权限启动它。在此消息“正在安装,这可能需要几分钟……”出现然后消失后,Ubuntu 会在第一次启动时提示您输入用户名和密码。现在我们可以开始安装 Emscripten 了。
安装 Emscripten
更新 Ubuntu 包并安装 Python 2.7
由于这是全新的 Ubuntu 安装,在安装 Python 之前我们需要更新包。运行这 3 条命令。这可能需要相当长的时间,但请勿中途取消。
sudo apt update
sudo apt upgrade
sudo apt install python2.7 python-pip
您可能需要安装 Git。运行以下命令
sudo apt-get install git-core
接下来,从 GitHub 获取 Emscripten。
git clone https://github.com/emscripten-core/emsdk.git
最后,我们准备好安装 Emscripten 了。运行以下命令下载并安装预编译的工具链。
# change to the newly cloned emsdk directory. cd emsdk # Download and install the latest SDK tools. ./emsdk install latest # Set up the compiler configuration to point to the "latest" SDK. ./emsdk activate latest # Activate PATH and other environment variables in the current terminal source ./emsdk_env.sh
最后一步,为了在您使用 Emscripten 工具集之前设置环境变量和路径,必须在每次 Ubuntu 启动时调用它。
为不受支持的 Linux 发行版或只是为了好玩而编译工具链
对于那些使用不受支持的 Linux 发行版的读者,您可以使用这些命令构建 Emscripten 工具链。根据您的存储类型(如 SSD 或 HDD)、CPU 核心数量和 RAM 容量,构建过程最多可能需要 3 小时。
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --build=Release sdk-incoming-64bit binaryen-master-64bit
Hello World 程序
在本节中,我们将构建一个 C“hello world
”程序,然后是 C++ 版本,并比较它们的输出文件大小。
Hello World C 程序
这是我们使用的 C 源代码。这个程序有一个我必须告诉您的怪癖。printf()
格式字符串必须以换行符结尾,它起到刷新控制台输出的标志作用,否则您将得不到任何输出。
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
}
我们将源代码保存在 hello.c 中。这是构建 hello.c 的命令。它大约需要 5 分钟,因为它会构建所需的 C 静态库,文件扩展名为 bc。后续构建应该会很快。
emcc hello.c -s WASM=1 -o hello.html
编译的输出是 hello.html、hello.js 以及最后的 hello.wasm,它是 Webassembly 文件。
Hello World C++ 程序
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
}
我们将 C++ 源代码保存在 hello2.cpp 中。这是构建 hello2.cpp 的命令。与 C 版本一样,构建 C++ 静态库需要一些时间。
emcc hello2.cpp -s WASM=1 -o hello2.html
编译的输出是 hello2.html、hello2.js 以及最后的 hello2.wasm。单个文件编译对于一个文件来说是可以的。要使用 make
命令构建多个源文件,只需用文本编辑器打开现有的 Makefile,并将所有“gcc”或“g++”的出现替换为“emcc”。C++ 输出文件比 C 文件大 400KB,这是因为仅仅包含 iostream
头文件就引入了模板膨胀。
要查看 HTML,请打开 Visual Studio 2019 并创建一个 ASP.NET 项目,然后将刚才提到的 HTML、JavaScript 和 wasm 文件添加到新创建的项目中。右键单击 hello.html 在浏览器中查看。对 hello2.html 执行相同的操作。Visual Studio 2017 的开发 Web 服务器在提供 wasm 文件方面存在问题。请使用 VS2019 或其他 Web 服务器。对于其他 Web 服务器,您可能需要在其配置文件中为 Webassembly 添加 MIME 类型(Content-Type=application/wasm),如果尚未添加的话。执行此操作的确切方法因每个 Web 服务器而异,请务必查阅手册或说明指南。如果您不想设置 MIME 类型并想查看 HTML 输出,请尝试在 emcc 构建期间将 WASM=0
设置为生成 asm.js 代码。asm.js 文件类型是所有 Web 服务器都能轻松提供的合法 JavaScript 文件。asm.js 处于维护模式,这意味着所有新颖的功能都将只添加到 Webassembly。在接下来的 2 个部分中,我们将探讨 C/C++ 与 JavaScript 的交互。
从 C/C++ 调用 JavaScript 代码
要从 C++ 调用 JavaScript,请将您的 JavaScript 代码放在 EM_ASM()
中。由于 EM_ASM()
不是合法的 C++ 代码,因此您必须用宏将其保护起来,以阻止 C++ 编译器解析它。在我的情况下,我在 Emscripten Makefile 中声明了 __EMSCRIPTEN__
。下面的示例查找一个名为 MyMusic
的 WebAudio
元素并调用其 play
方法来播放 MP3。
#ifdef __EMSCRIPTEN__
EM_ASM(
document.getElementById("MyMusic").play();
);
#endif
要将一些参数传递给 EM_ASM
JavaScript 片段,请调用带有下划线后缀的 EM_ASM_
和您的参数:$0
是第一个参数的占位符,$1
是第二个,依此类推。
EM_ASM_({
console.log('I received: ' + $0);
}, 100);
要从 JavaScript 代码片段返回整数或双精度值,请调用 EM_ASM_INT
或 EM_ASM_DOUBLE
。
int x = EM_ASM_INT({
console.log('I received: ' + $0);
return $0 + 1;
}, 100);
printf("%d\n", x);
从 JavaScript 调用 C/C++ 代码
为了让 JavaScript 调用 C 函数,您必须在编译期间导出 C 函数,然后在 JavaScript 中调用 Module.cwrap()
将其放入一个可调用的包装器中。下面是 gen_enum_conv
的签名。由于所有 C++ 编译器都会进行名称修饰/更改:为了避免这种情况,使函数名保持不变以便 JavaScript 找到它,我们必须在函数签名之前声明 extern "C"
。
extern "C" const char* gen_enum_conv(const char* cs);
这是具有 main
和 gen_enum_conv
导出函数的 Makefile:这两个名称都以在命名约定中很常见的下划线开头。
CC=emcc
SOURCES:=~/EnumStrConv.cpp
SOURCES+=~/ParseEnum.cpp
LDFLAGS=-O2 --llvm-opts 2
OUTPUT=~/EnumConvGen.html
EMCC_DEBUG=1
all: $(SOURCES) $(OUTPUT)
$(OUTPUT): $(SOURCES)
$(CC) $(SOURCES) --bind -s NO_EXIT_RUNTIME=1 -s
EXPORTED_FUNCTIONS="['_main', '_gen_enum_conv']" -s
ALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1
-D__EMSCRIPTEN__ -std=c++11 $(LDFLAGS) -o $(OUTPUT)
clean:
rm $(OUTPUT)
下面是用于包装此函数并由按钮单击调用的 JavaScript 代码。要深入了解更多代码,请访问 EnumConvGen GitHub。EnumConvGen
是一个 C++ 项目,用于生成 C++ enum
到 string
的转换函数,反之亦然。
<script>
var gen_enum_conv_func;
function btnClick()
{
var input_str = document.getElementById("InputTextArea").value;
document.getElementById("OutputTextArea").value = gen_enum_conv_func(input_str);
}
$( document ).ready(function() {
gen_enum_conv_func = Module.cwrap('gen_enum_conv', 'string', ['string']);
$('[name="GenButton"]').click(btnClick);
});
</script>
调用 C++ 成员函数呢?
我刚才向您展示的是如何调用 C 函数。那么如何从 JavaScript 调用 C++ 成员函数呢?您必须使用 embind 来完成此操作。有关如何操作,请参阅其 文档。我个人不使用 embind,因为它很复杂。我通常的做法是将所有 C++ 调用都封装在 C 函数体中。我还没有遇到过需要我专门使用 embind 来完成所需任务的情况。
跨平台
Emscripten 仅限于调用 C++ 标准库和 emscripten 移植的 C/C++ 库(见下文列表)的可移植函数。特定于操作系统的函数(如 win32)和包含汇编代码的函数是不可能的。库列表主要集中在图形、音频、网络和字体领域,正如您所见,这些都是为了支持游戏编程而设计的。
- SDL2
- regal
- HarfBuzz
- SDL2_mixer
- SDL2_image
- Cocos2d
- FreeType
- asio
- SDL2_net
- SDL2_ttf
- Vorbis
- Ogg
- Bullet
- libpng
- zlib
好了,各位!请继续关注“带上您的 XXX”系列文章的第二部分!与此同时,尽情享受 Webassembly 的乐趣吧!
《将你的...》系列的其他文章
- 将你的 C++ 代码带到 Web
- 将 C++ 图形带到 Web
- 将你的动画带到 H264/HEVC 视频
- 将你的现有应用程序带到 Microsoft Store
- 将你的 C++ OpenGL 代码带到 Web
历史
- 2019年6月29日:初稿