前端从零学C++(四):怎么用C++写WebAssembly

1,075 阅读6分钟

背景

我是一个 C/C++ 都没实战经验的前端开发,C 在几年前有了解过一些语法知识,现在开始从零开始学习 C++,往期精彩:

注:本文要求至少有一门编程语言的基础。

我的电脑环境:

  • mac
  • vscode 1.88.1

编写 WebAssembly(Wasm)模块并在网页中使用它是一个非常有趣的过程。WebAssembly 是一种低级字节码格式,可以在网页中高效地运行,适用于性能要求高的应用程序。C++ 是编写 WebAssembly 的常用语言之一,因为它可以编译为 WebAssembly 模块并在浏览器中执行。以下是一个简要的步骤指南,展示了如何使用 C++ 编写 WebAssembly 模块。

1. 准备环境

要编写 WebAssembly 模块,你需要以下工具:

  • Emscripten:这是一个开源工具链,允许你将 C++ 代码编译成 WebAssembly。你可以通过以下命令安装 Emscripten:

    # 安装 Emscripten
    git clone https://github.com/emscripten-core/emsdk.git
    cd emsdk
    ./emsdk install latest
    ./emsdk activate latest
    source ./emsdk_env.sh
    

2. 编写 C++ 代码

编写一个简单的 C++ 程序,作为 WebAssembly 模块。创建一个名为 hello.cpp 的文件,内容如下:

#include <iostream>
#include <emscripten.h>

// 这是一个导出的函数,可以在 JavaScript 中调用
extern "C" {
    // 使用 EMSCRIPTEN_KEEPALIVE 确保这个函数不会被优化掉
    EMSCRIPTEN_KEEPALIVE
    int add(int a, int b) {
        return a + b;
    }
}

vscode 环境报错

image.png

这个错误提示通常是因为你的开发环境(如 Visual Studio Code 或其他编辑器)无法找到 C++ 标准库的头文件或你设置的 includePath 不正确。下面是一些常见的解决方案,帮助你更新 includePath 并修复这个问题:

1. 确认 Emscripten 环境设置

确保你已经正确安装并配置了 Emscripten。你可以使用以下命令检查 Emscripten 是否正常工作:

emcc --version

2. 更新 includePath

如果你使用的是 Visual Studio Code(VSCode),你需要更新 C++ 插件的 includePath 设置,以便正确找到头文件。按照以下步骤操作:

2.1. 打开 c_cpp_properties.json
  1. 打开 VSCode。
  2. 点击左侧的“资源管理器”图标。
  3. 进入你项目目录中的 .vscode 文件夹。如果没有这个文件夹,你可以创建一个。
  4. .vscode 文件夹中,找到或创建 c_cpp_properties.json 文件。
2.2. 编辑 c_cpp_properties.json

确保你的 c_cpp_properties.json 文件包含正确的 includePath 配置。以下是一个示例配置,你需要根据你的 Emscripten 安装路径进行调整:

{
    "version": 4,
    "configurations": [
        {
            "name": "Emscripten",
            "includePath": [
                "${workspaceFolder}/**",
                "/path/to/emsdk/upstream/emscripten/system/include/**",
                "/path/to/emsdk/upstream/emscripten/system/include/libc/**",
                "/path/to/emsdk/upstream/emscripten/system/include/libcxx/**",
                "/path/to/emsdk/upstream/emscripten/system/include/libcxxabi/**",
                "/path/to/emsdk/upstream/emscripten/system/include/SDL2/**"
            ],
            "defines": [],
            "compilerPath": "/path/to/emsdk/upstream/emscripten/emcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ]
}

请将 /path/to/emsdk/ 替换为你实际的 Emscripten 安装路径。你可以在 Emscripten 安装目录中找到 include 目录的确切位置。

3. 确保 Emscripten 配置正确

在你的终端中运行以下命令,确保 Emscripten 配置正确,并且环境变量已经设置:

source /path/to/emsdk/emsdk_env.sh

4. 重启编辑器

修改完 c_cpp_properties.json 文件后,重启你的编辑器,以确保新的设置生效。

5. 检查编译器路径

确保在 c_cpp_properties.json 中设置的 compilerPath 指向了正确的编译器。如果你使用的是 Emscripten,它通常位于 /path/to/emsdk/upstream/emscripten/emcc

6. 使用其他编辑器的设置

如果你使用的是其他编辑器,类似的设置也需要配置。例如,在 CLion 中,你需要在 CMake 配置中指定正确的包含路径。

3. 编译 C++ 代码

使用 Emscripten 工具链将 C++ 代码编译成 WebAssembly 模块。运行以下命令:

emcc hello.cpp -o hello.html -s EXPORTED_FUNCTIONS="['_add']" -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
  • -o hello.html 指定输出文件,Emscripten 会生成一个包含 HTML 和 JavaScript 的文件,以便在浏览器中运行 WebAssembly 模块。
  • -s EXPORTED_FUNCTIONS="['_add']" 指定需要导出的函数(注意函数名需要加上下划线 _)。
  • -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' 允许使用 ccallcwrap 方法调用 WebAssembly 中的函数。

提示zsh: command not found: emcc

这个提示表明你的系统无法找到 emcc 命令。这通常是因为 Emscripten 没有正确安装,或者环境变量没有设置好。以下是解决这个问题的步骤:

1. 设置环境变量

在本地path/emsdk/下运行以下指令:

source ./emsdk_env.sh

这会将 emcc 和其他 Emscripten 工具添加到你的 PATH 环境变量中。

2. 确保环境变量设置正确

每次打开一个新的终端窗口时,你需要重新设置 Emscripten 环境变量。为了避免每次都手动设置,你可以将 source ./emsdk_env.sh 添加到你的 shell 启动脚本中(例如 .zshrc 文件),以便每次打开终端时自动设置环境变量:

  1. 打开或编辑你的 .zshrc 文件:
nano ~/.zshrc
  1. 在文件末尾添加以下行:
source ~/emsdk/emsdk_env.sh
  1. 保存并关闭文件(在 nano 中,你可以按 Ctrl + X,然后按 Y 确认保存)。

  2. 使更改生效:

source ~/.zshrc

3. 检查 emcc 命令是否可用

在终端中输入以下命令,检查 emcc 是否可以正常运行:

emcc --version

如果一切正常,你应该能够看到 emcc 的版本信息。

4. 其他可能的问题

  • 路径问题:确保 emsdk 目录和 emsdk_env.sh 文件的位置正确,并且你设置了正确的路径。
  • 权限问题:确保你有权限访问 emsdk 目录及其文件。

4. 创建 HTML 文件

Emscripten 会自动生成一个 HTML 文件,但你也可以手动创建一个简单的 HTML 文件来加载和使用生成的 WebAssembly 模块。创建一个名为 index.html 的文件,内容如下:

<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Example</title>
    <script src="hello.js"></script>
    <script>
        // 确保 WebAssembly 模块加载完成后再调用
        Module.onRuntimeInitialized = function() {
            // 使用 ccall 调用 WebAssembly 中的 add 函数
            var result = Module.ccall('add', 'number', ['number', 'number'], [5, 3]);
            console.log('Result of add(5, 3):', result);
        };
    </script>
</head>
<body>
    <h1>WebAssembly Example</h1>
</body>
</html>

5. 运行 WebAssembly 模块

使用一个 HTTP 服务器来运行你的 HTML 文件。你可以使用 Python 内置的 HTTP 服务器:

python3 -m http.server

在浏览器中访问 http://localhost:8000,你应该能看到控制台输出结果 Result of add(5, 3): 8

6. 进一步探索

  • 数据交换:你可以通过 ccallcwrap 方法在 JavaScript 和 WebAssembly 之间交换数据。
  • 内存管理:了解 WebAssembly 的内存模型,以便高效地处理大数据或复杂计算。
  • 优化:Emscripten 提供了许多优化选项,可以提高 WebAssembly 模块的性能。

通过这些步骤,你可以将 C++ 代码编译成 WebAssembly,并在浏览器中运行它。