WebAssembly - 与 C++ 合作
你好,有抱负的程序员们!我很高兴能成为你们在这个激动人心的旅程中的向导,一起走进 WebAssembly 和 C++ 的世界。作为一个教授计算机科学超过十年的人,我可以向你保证,尽管这个主题一开始可能看起来很令人畏惧,但我们会把它分解成小块,即使是完全的初学者也能消化。那么,让我们卷起袖子,跳进去吧!
WebAssembly 是什么?
在我们跳入代码之前,让我们先了解一下 WebAssembly 是关于什么的。想象你正在尝试和一个不懂你语言的人说话。你需要一个翻译,对吧?WebAssembly 就像是你网络浏览器中的那个翻译。它允许像 C++ 这样的语言编写的程序在网络浏览器中以接近本地速度运行。酷吧?
为什么 C++ 和 WebAssembly?
你可能会想,"为什么是 C++?" 好吧,C++ 就像编程语言中的瑞士军刀——它强大、灵活,并且已经存在很长时间了。当与 WebAssembly 结合时,它允许我们把高性能应用程序带到网络上。就像给你的网站装上涡轮增压!
设置我们的环境
在我们写下第一行代码之前,我们需要设置我们的工作空间。别担心,我会一步一步地引导你:
- 安装 Emscripten:这是我们把 C++ 转换成 WebAssembly 的魔法棒。
- 设置文本编辑器:我推荐 Visual Studio Code,但任何文本编辑器都可以。
- 打开终端:我们将用它来编译我们的代码。
我们的第一个 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
这可能看起来很复杂,但我们本质上是在:
- 在 JavaScript 中创建一个数组。
- 在 WebAssembly 的堆中分配内存。
- 将我们的数组复制到该内存。
- 调用我们的函数。
- 释放分配的内存。
结论
恭喜你!你已经迈出了进入 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