WebAssembly - 与 C++ 合作

你好,有抱负的程序员们!我很高兴能成为你们在这个激动人心的旅程中的向导,一起走进 WebAssembly 和 C++ 的世界。作为一个教授计算机科学超过十年的人,我可以向你保证,尽管这个主题一开始可能看起来很令人畏惧,但我们会把它分解成小块,即使是完全的初学者也能消化。那么,让我们卷起袖子,跳进去吧!

WebAssembly - Working with C++

WebAssembly 是什么?

在我们跳入代码之前,让我们先了解一下 WebAssembly 是关于什么的。想象你正在尝试和一个不懂你语言的人说话。你需要一个翻译,对吧?WebAssembly 就像是你网络浏览器中的那个翻译。它允许像 C++ 这样的语言编写的程序在网络浏览器中以接近本地速度运行。酷吧?

为什么 C++ 和 WebAssembly?

你可能会想,"为什么是 C++?" 好吧,C++ 就像编程语言中的瑞士军刀——它强大、灵活,并且已经存在很长时间了。当与 WebAssembly 结合时,它允许我们把高性能应用程序带到网络上。就像给你的网站装上涡轮增压!

设置我们的环境

在我们写下第一行代码之前,我们需要设置我们的工作空间。别担心,我会一步一步地引导你:

  1. 安装 Emscripten:这是我们把 C++ 转换成 WebAssembly 的魔法棒。
  2. 设置文本编辑器:我推荐 Visual Studio Code,但任何文本编辑器都可以。
  3. 打开终端:我们将用它来编译我们的代码。

我们的第一个 WebAssembly 程序

让我们从一个简单的 "Hello, World!" 程序开始。以下是代码:

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

extern "C" {
EMSCRIPTEN_KEEPALIVE
void sayHello() {
printf("Hello, WebAssembly World!\n");
}
}

现在,让我们分解一下:

  • #include <emscripten/emscripten.h>:这行代码包含了 Emscripten 的头文件,给了我们访问 WebAssembly 相关函数的能力。
  • extern "C":这告诉编译器对我们的函数使用 C 风格的命名。
  • EMSCRIPTEN_KEEPALIVE:这就像在我们的函数上放一个“不要删除”的标志,确保它可以被 JavaScript 访问。
  • void sayHello():这就是打印问候语的功能。

编译我们的代码

现在是挥动我们的魔法棒的时候了!在终端中,运行:

emcc hello.cpp -o hello.html -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']"

这个命令可能看起来像哈利·波特里的一个咒语,但我来解释一下:

  • emcc:这是我们编译器。
  • hello.cpp:我们的源文件。
  • -o hello.html:这会创建一个我们可以用浏览器打开的 HTML 文件。
  • 剩下的是特殊标志,让我们的 WebAssembly 和 JavaScript 一起好好玩。

运行我们的 WebAssembly

在浏览器中打开生成的 hello.html,打开控制台,并输入:

Module.ccall('sayHello', null, null, null);

如果你在控制台中看到了 "Hello, WebAssembly World!",恭喜你!你刚刚在浏览器中运行了 C++!

一个更复杂的例子:斐波那契计算器

现在我们已经湿了脚,让我们尝试一些更具挑战性的东西——一个斐波那契数计算器。

#include <emscripten/emscripten.h>

extern "C" {
EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
}

这个函数通过递归计算第 n 个斐波那契数。这不是最有效的方法,但非常适合演示!

像之前一样编译它,然后用 JavaScript 这样调用:

console.log(Module.ccall('fibonacci', 'number', ['number'], [10]));

这应该会打印出第 10 个斐波那契数(顺便说一下,是 55)。

处理数组

让我们升级一下,处理数组。以下是一个计算数组总和的函数:

#include <emscripten/emscripten.h>

extern "C" {
EMSCRIPTEN_KEEPALIVE
int sumArray(int* arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
}

要从 JavaScript 使用它,我们需要做一些额外的工作:

let arr = new Int32Array([1, 2, 3, 4, 5]);
let buffer = Module._malloc(arr.length * arr.BYTES_PER_ELEMENT);
Module.HEAP32.set(arr, buffer >> 2);
let sum = Module.ccall('sumArray', 'number', ['number', 'number'], [buffer, arr.length]);
Module._free(buffer);
console.log(sum);  // 应该打印 15

这可能看起来很复杂,但我们本质上是在:

  1. 在 JavaScript 中创建一个数组。
  2. 在 WebAssembly 的堆中分配内存。
  3. 将我们的数组复制到该内存。
  4. 调用我们的函数。
  5. 释放分配的内存。

结论

恭喜你!你已经迈出了进入 WebAssembly 和 C++ 世界的第一步。我们已经覆盖了很多内容,从基本的 "Hello, World!" 到处理数组。记住,学习编码就像学习一门新语言——它需要练习和耐心。如果你一开始不理解所有东西,不要气馁。继续尝试,继续编码,最重要的是,继续享受乐趣!

下面是一个总结我们使用的主要方法的表格:

方法 描述
emcc Emscripten 编译器命令
EMSCRIPTEN_KEEPALIVE 防止函数被优化掉的宏
Module.ccall JavaScript 方法,用于调用 C++ 函数
Module._malloc 在 WebAssembly 堆中分配内存
Module._free 在 WebAssembly 堆中释放分配的内存
Module.HEAP32 WebAssembly 内存中的 Int32Array 视图

记住,WebAssembly 和 C++ 为网络开发打开了一个充满可能性的世界。天空才是极限!继续编码,继续学习,谁知道呢?也许几年后你就是教这门课的人!

Credits: Image by storyset