WebAssembly - 使用 C++ 進行開發

大家好,有抱負的程式設計師們!我很高興能成為您們在 WebAssembly 和 C++ 這個令人興奮領域中的引路人。作為一個教了十多年計算機科學的人,我可以向您們保證,雖然這個主題起初可能看起來有些嚇人,但我們會把它分解成即使是完全的初學者也能夠消化的一小部分。那麼,我們就挽起袖子,一起跳進去吧!

WebAssembly - Working with C++

WebAssembly 是什麼?

在我們進行代碼編寫之前,讓我們先來了解 WebAssembly 是什麼。想像一下,如果你試圖和一個不懂你語言的人交談。你會需要一個翻譯,對吧?WebAssembly 就像是你瀏覽器中的那個翻譯。它讓寫在像 C++ 這樣的語言中的程序能在瀏覽器中以接近本地速度運行。酷炫吧?

為什麼 WebAssembly 和 C++?

你可能會想,"為什麼選擇 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. 釋放分配的內存

結論

恭喜你們!你們已經開始踏上了使用 C++ 和 WebAssembly 的旅程。我們已經涉足了從基本的 "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