WebAssembly實踐指南——C++和Rust透過wasmtime實現相互呼叫例項

金筆書生呂落第發表於2023-09-19

C++和Rust透過wasmtime實現相互呼叫例項

1 wasmtime介紹

wasmtime是一個可以執行WebAssembly程式碼的執行時環境。

WebAssembly是一種可移植的二進位制指令集格式,其本身與平臺無關,類似於Java的class檔案位元組碼。

WebAssembly本來的設計初衷是想讓瀏覽器可以執行C語言這種編譯型語言的程式碼。通常我們的C語言程式碼會使用gcc或clang等編譯器直接編譯連結成與平臺相關的二進位制可執行檔案,這種與平臺相關的二進位制檔案瀏覽器是無法直接執行的。如果想讓瀏覽器執行C語言程式碼,就需要使用可將C語言編譯成WebAssembly指令的編譯器,編譯好的程式碼是wasm格式。然後就可以使用各種wasm執行時來執行wasm程式碼,這就類似於JVM虛擬機器執行class檔案。

由於指令集和執行時環境本身與web場景並不繫結,因此隨著後來的發展,WebAssembly指令集出現了可以脫離瀏覽器的獨立執行時環境,WebAssembly的用途也變得更加廣泛。

相比於瀏覽器的執行時,wasmtime是一個獨立執行時環境,它可以脫離Web環境來執行wasm程式碼。它本身提供了命令列工具和API兩種方式來執行wasm程式碼。本文主要介紹如何使用API方式來執行wasm程式碼。

2 wasmtime安裝

2.1 wasmtime-cli安裝

wasmtime-cli包含wasmtime命令,可以讓我們直接在shell中執行wasm格式的程式碼。我們這裡安裝wasmtime主要是為了測試方便。

  1. 在shell中執行如下命令

    curl https://wasmtime.dev/install.sh -sSf | bash
    
  2. wasmtime的可執行檔案會被安裝在${HOME}/.wasmtime目錄下

  3. 執行以上命令後會在${HOME}/.bashrc${HOME}/.bash_profile檔案中幫我們新增以下環境變數

    export WASMTIME_HOME="${HOME}/.wasmtime"
    export PATH="$WASMTIME_HOME/bin:$PATH"
    
  4. 如果希望所有使用者(包括root)可以使用wasmtime命令,可以將以上環境變數設定到/etc/profile.d中,我們可以在該目錄下建立wasmtime.sh檔案,並新增一下程式碼

    export WASMTIME_HOME=/home/<xxx>/.wasmtime  # 將xxx替換成自己的home目錄
    export PATH="$WASMTIME_HOME/bin:$PATH"
    
  5. 可以使用如下命令直接執行wasm檔案

    wasmtime hello.wasm
    

2.2 wasmtime庫安裝

如果想在程式碼中載入wasm檔案並執行其中的程式碼,我們需要為我們使用的語言安裝wasmtime庫。注意這裡的wasmtime庫是為了讓我們從程式碼中能夠載入wasm檔案並在wasmtime執行時中執行。wasmtime並不是wasm編譯器,不能將C++或Rust程式碼編譯成wasm檔案,如果我們想將其他語言編譯成wasm程式碼,需要下載各個語言自己的wasm編譯器,具體安裝方式在本文第3節。

目前wasmtime支援的語言有:

  • Rust
  • C
  • C++
  • Python
  • .NET
  • Go

我們這裡以Rust和C++為例介紹如何安裝wasmtime庫

Rust

在Rust中使用wasmtime庫非常簡單,我們只需要在Cargo.toml配置檔案中新增如下依賴

[dependencies]
wasmtime = "12.0.2"

C++

wasmtime的C++庫需要我們引入wasmtime-cpp這個專案,wasmtime-cpp依賴wasmtime的C API,因此需要先安裝C API。

  1. 可以在wasmtime的release中找到字尾為-c-api的包,比如我們安裝的平臺是x86_64-linux,那麼我們可以下載如下檔案

    wget https://github.com/bytecodealliance/wasmtime/releases/download/v12.0.2/wasmtime-v12.0.2-x86_64-linux-c-api.tar.xz
    
  2. 解壓以上檔案並將其移動到/usr/local目錄下

    tar -xvf wasmtime-v12.0.2-x86_64-linux-c-api.tar.xz
    sudo mv ./wasmtime-v12.0.2-x86_64-linux-c-api /usr/local/wasmtime
    
  3. /etc/profile.d/wasmtime.sh中新增環境變數

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/wasmtime/lib
    export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/wasmtime/lib
    export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/wasmtime/include
    export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/wasmtime/include
    
  4. 下載wasmtime-cpp專案的include/wasmtime.hh檔案,將其放到wasmtime.h所在的目錄下,按照我們的安裝步驟,需要放置到/usr/local/wasmtime/include目錄下

  5. 如此就可以在我們的C++專案中引入wasmtime庫了

    #include <wasmtime.hh>
    

3 wasm編譯器安裝

Rust

安裝

Rust語言的編譯器目前其實是一個LLVM的編譯前端,它將程式碼編譯成LLVM IR,然後經過LLVM編譯成相應的目標平臺程式碼。

因此我們並不需要替換Rust語言本身的編譯器,只需要在編譯時設定目標平臺為wasm即可。我們在安裝rust時,通常只會安裝本機平臺支援的目標,因此我們需要先安裝wasm目標。

# 列出所有可安裝的target列表
rustup target list

使用上面的命令後可以看到很多可以安裝的target列表,其中已經安裝的target後面會有(installed)標示。注意到其中有3個wasm相關的target。

wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-wasi
  1. wasm32-unknown-emscripten:這個target是為了在Emscripten工具鏈下編譯Wasm。Emscripten是一個將C/C++程式碼編譯為Wasm和JavaScript的工具鏈。使用這個target,你可以在瀏覽器環境中執行編譯後的Wasm程式碼。
  2. wasm32-unknown-unknown:這個target是為了在沒有任何作業系統支援的情況下執行WebAssembly程式碼而設計的。這種情況下,WebAssembly程式碼將執行在一個“裸機”環境中,沒有任何作業系統提供的支援。因此,如果你需要在裸機環境中執行WebAssembly程式碼,那麼使用這個target是一個不錯的選擇。
  3. wasm32-wasi:這個target是為了在WebAssembly System Interface (WASI)上執行WebAssembly程式碼而設計的。WASI是一個標準介面,它提供了一些作業系統級別的功能,如檔案系統和網路訪問等。因此,如果你需要在WebAssembly中訪問這些作業系統級別的功能,那麼使用這個target是一個不錯的選擇。

由於我們不需要在Web環境中執行Rust程式碼,因此我們選擇安裝wasm32-unknown-unknownwasm32-wasi兩個目標。執行以下兩條指令,將這兩個目標平臺加入到當前使用的Rust工具鏈中。

rustup target add wasm32-unknown-unknown
rustup target add wasm32-wasi

使用

當我們需要將一個Rust專案編譯成wasm時,可以選擇執行如下的兩種編譯命令

# 在專案根目錄執行
cargo build --target wasm32-unknown-unknown  # 將在target/wasm32-unknown-unknown目錄中生成build中間結果和wasm檔案

# 或者執行
cargo build --target wasm32-wasi  # 將在target/wasm32-wasi目錄中生成build中間結果和wasm檔案

C++

安裝

目前,要將C++專案編譯成WebAssembly,最常用的工具鏈是emscripten。emscripten支援將C,C++或任何使用了LLVM的語言編譯成瀏覽器,Node.js或wasm執行時可以執行的程式碼。

Emscripten is a complete compiler toolchain to WebAssembly, using LLVM, with a special focus on speed, size, and the Web platform.

WebAssembly目前支援兩種標準API:

  • Web APIs
  • WASI APIs

Emscripten對JavaScript API做了重構,將其包裝在與WASI介面一樣的API中,然後Emscripten在編譯程式碼時,將盡可能的使用WASI APIs,以此來避免不必要的API差異。因此Emscripten編譯出來的wasm檔案大部分時候可以同時執行在Web和非Web環境中。

使用如下命令下載emsdk

git clone https://github.com/emscripten-core/emsdk.git

cd emsdk

使用如下命令安裝最新的工具

git pull

./emsdk install latest

./emsdk activate latest

如果臨時將emsdk的工具目錄加入環境變數,可以執行

source ./emsdk_env.sh

或者可以在/etc/profile.d目錄中建立emsdk.sh檔案,並加入如下環境變數的配置,需要將<emsdk_installed_dir>替換為emsdk所在的目錄。

export PATH=$PATH:<emsdk_installed_dir>/emsdk:<emsdk_installed_dir>/emsdk/node/16.20.0_64bit/bin:<emsdk_installed_dir>/emsdk/upstream/emscripten
export EMSDK=<emsdk_installed_dir>/emsdk
export EMSDK_NODE=<emsdk_installed_dir>/emsdk/node/16.20.0_64bit/bin/node

使用如下命令測試是否安裝成功,如果輸出下面的資訊,說明我們已經可以正常使用emscripten的工具鏈。

> emcc -v

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.45 (ef3e4e3b044de98e1811546e0bc605c65d3412f4)
clang version 18.0.0 (https://github.com/llvm/llvm-project d1e685df45dc5944b43d2547d0138cd4a3ee4efe)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: <emsdk_installed_dir>/emsdk/upstream/bin

使用

由於我們不使用Web執行時,下面將只介紹將C或C++程式碼編譯成獨立wasm二進位制檔案的使用方法。

  1. 簡單使用
emcc -O3 hello.cpp -o hello.wasm

當我們將輸出目標的字尾名指定為wasm時,編譯器會自動幫我們設定如下連線選項,上面的命令與下面的命令時等價的

emcc -O3 hello.cpp -o hello.wasm -s STANDALONE_WASM

這樣編譯出來的結果不會包含js檔案,只會包含一個可被wasmtime執行的wasm檔案。

  1. 結合cmake使用

更常用的方式通常是將整個C++專案編譯成wasm,因此我們需要將工具鏈與cmake結合來構建整個專案。

假設我們有一個cmake專案有如下專案結構

hello_project
   |-hello.cpp
   |-CMakeLists.txt

其中hello.cpp中有如下程式碼

#include <stdio.h>

int main() {
  printf("hello, world!\n");
  return 0;
}

CMakeLists.txt應該按照下面的方式進行改寫

cmake_minimum_required(VERSION 3.26)
project(hello_project)

add_definitions(-std=c++17)
set(CMAKE_CXX_STANDARD 17)

if (DEFINED EMSCRIPTEN)
    add_executable(hello hello.cpp)

    set(CMAKE_EXECUTABLE_SUFFIX  ".wasm")

    set_target_properties(foo PROPERTIES COMPILE_FLAGS "-Os")
    set_target_properties(foo PROPERTIES LINK_FLAGS "-Os -s WASM=1 -s STANDALONE_WASM")
else()
    add_executable(hello hello.cpp)
endif ()

以上CMakeLists.txt表示,當我們使用emscripten工具鏈進行編譯時,將輸出.wasm檔案,且新增對應的編譯和連線選項。當我們使用其他工具鏈編譯時,將直接輸出對應平臺的可執行檔案。

按照上面的方式寫好CMakeLists.txt後,需要使用以下命令來執行編譯的過程

# 在專案根目錄下
mkdir build
cd build

# 執行emcmake命令會幫我們自動配置cmake中指定的工具鏈為emscripten的工具鏈,這樣就確保了使用的編譯工具為emcc或em++,同時使用的標準庫更改為emscripten提供的標準庫
emcmake cmake ..
# 再執行make進行編譯,編譯後可以發現build目錄中生成了hello.wasm檔案
make

使用wasmtime-cli執行hello.wasm檔案

> wasmtime hello.wasm

hello, world!

4 小試牛刀

實驗場景

需要測試Rust程式碼被編譯成wasm,C++程式碼被編譯成wasm,在wasmtime中正確執行。其中C++程式碼可以呼叫Rust程式碼中的函式,然後外部可以呼叫C++程式碼中的函式。

  1. Rust專案:包含一個add函式,做兩個整數的加法並返回結果,可以被外部呼叫。需要編譯成wasm。
  2. C++專案:包含一個foo函式,呼叫Rust中的add函式並返回結果。需要編譯成wasm。
  3. wasmtime專案:需要載入前面兩個專案生成的wasm檔案,並執行foo函式,看是否能獲取正確的結果。

Rust專案編譯成wasm

建立一個專案叫做demo-rust-wasmtime

cargo new demo-rust-wasmtime --lib

建立好的專案結構如下

demo-rust-wasmtime
├── Cargo.lock
├── Cargo.toml
└── src
    └── lib.rs

首先需要在Cargo.toml中配置生成的庫為cdylib,這表示按照C語言的FFI來生成動態庫,要想不同語言之間能夠互相呼叫對方的函式,通常需要將不同的語言按照相同的FFI來進行編譯,確保函式呼叫的方式是相同的。這裡同時我們將Rust專案的名稱修改為calc

[package]
name = "calc"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]

[dependencies]

lib.rs中實現我們需要的add函式

#[no_mangle]
pub extern "C" fn add(left: i32, right: i32) -> i32 {
    left + right
}

這裡有兩個地方需要注意:

  • #[no_mangle]會通知Rust編譯器,其後面的函式編譯時名字不要進行混淆,確保使用add這個名稱進行連結時能找到正確的函式。
  • extern "C"表示編譯器需要確保函式在編譯時使用與C語言相同的呼叫約定(ABI),從而使得函式可以與C語言程式碼無縫地進行互動,當然如果我們將不同的語言都遵照C語言的ABI進行編譯,那麼它們之間就可以互相呼叫。

C語言的呼叫約定規定了函式引數的傳遞方式、返回值的處理方式以及堆疊的清理方式。

這樣就定義好了Rust專案中可以讓外部使用的add方法。

我們使用如下命令對專案進行編譯

cargo build --target wasm32-unknown-unknown
# 或
cargo build --target wasm32-wasi

這裡兩種target都可以使用,因為我們的專案中並沒有使用任何系統的API,所以通常使用第一種target即可。

編譯後可以在target/wasm-xxx/debug/目錄下看到生成的calc.wasm檔案。

可以使用wasmtime-cli實驗一下是否能夠呼叫add方法:

> wasmtime calc.wasm --invoke add 101 202

warning: using `--invoke` with a function that takes arguments is experimental and may break in the future
warning: using `--invoke` with a function that returns values is experimental and may break in the future
303

可以看到已經正確輸出了結果,說明這個Rust專案已經被正確編譯成了wasm。

C++專案編譯成wasm

建立一個專案叫做demo-cpp-wasmtime,使用cmake作為構建工具,其目錄結構如下

demo-cpp-wasmtime
├── CMakeLists.txt
├── toolbox.cpp
└── toolbox.h

正如第3節講到的,我們需要使用emscripten工具鏈代替gcc工具鏈來將這個C++專案編譯成wasm。

cmake配置

因此我們需要按照如下方式配置CMakeLists.txt檔案

cmake_minimum_required(VERSION 3.26)
project(demo_cpp_wasmtime)

add_definitions(-std=c++17)
set(CMAKE_CXX_STANDARD 17)

if (DEFINED EMSCRIPTEN)
    add_executable(toolbox toolbox.cpp toolbox.h)

    set(CMAKE_EXECUTABLE_SUFFIX  ".wasm")

    set_target_properties(toolbox PROPERTIES COMPILE_FLAGS "-Os -s SIDE_MODULE=1")
    set_target_properties(toolbox PROPERTIES LINK_FLAGS "-Os -s WASM=1 -s SIDE_MODULE=1 -s STANDALONE_WASM --no-entry")
else()
    add_library(toolbox toolbox.cpp)
endif ()

這裡有幾點需要注意的

  1. 在使用emscripten時,我們使用add_executable指定編譯目標為可執行檔案,這是因為wasm本身是可執行的二進位製程式碼,在沒有特殊配置時,編譯後的wasm程式碼中會生成一個_start函式,這個函式就是執行時執行wasm程式碼的入口。這裡如果我們將add_executable替換成add_library,則使用emscripten編譯後只會生成libtoolbox.a庫檔案,而不會生成wasm程式碼。

  2. 針對emscripten編譯工具鏈,我們配置了編譯引數和連結引數

    • -Os表示開啟編譯最佳化

    • -s SIDE_MODULE=1表示將toolbox編譯成module,這樣生成的wasm就類似動態連結庫,可以讓wasmtime在執行時動態連結這份wasm程式碼。

      emscripten支援將程式碼編譯成兩種不同的module

      1. Main modules:系統庫會被連結進去
      2. Side modules:系統庫不會被連結進去

      通常一個完整的專案只能有一個Main module,這個Main module可以連結多個Side module

      這裡的編譯選項SIDE_MODULE可以被設定為1或者2,設定成2則編譯器會最佳化掉大量未被使用的程式碼或未被標記為EMSCRIPTEN_KEEPALIVE的程式碼,設定成1則會保留所有程式碼。

    • -s WASM=1表示只輸出wasm檔案,設定為0表示只輸出js程式碼,設定成2表示兩種程式碼都輸出

    • -s STANDALONE_WASM表示編譯的wasm是不依賴web環境而執行的

    • --no-entry編譯生成的wasm程式碼通常需要有一個入口函式,也就是C++中需要有main函式,然而我們這裡toolbox.cpp中將只有一個foo函式,因此我們需要使用這個連結引數來表示我們不需要入口函式。

程式碼實現

toolbox.h標頭檔案如下

#pragma once

extern "C" {
int foo(int right);
}

類似Rust,這裡我們宣告瞭一個函式foo,並使用extern "C"表示這個foo函式需要按照C語言ABI進行編譯。

接下來是toolbox.cpp的實現

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#define EM_IMPORT(NAME)
#endif


extern "C" {
EM_IMPORT(add) int add(int a, int b);
}

extern "C" {
EMSCRIPTEN_KEEPALIVE int foo(int right) {
    return add(1, right);
}
}

下面解釋一下程式碼中的幾個宏的作用:

  • #ifdef __EMSCRIPTEN__:當我們使用emscripten工具鏈編譯這個專案時,__EMSCRIPTEN__會被自動定義

  • EMSCRIPTEN_KEEPALIVEEM_IMPORT(NAME)

    這是標頭檔案emscripten.h中定義的宏,檢視原始碼可以發現

    #define EMSCRIPTEN_KEEPALIVE __attribute__((used))
    
    #ifdef __wasm__
    #define EM_IMPORT(NAME) __attribute__((import_module("env"), import_name(#NAME)))
    #else
    #define EM_IMPORT(NAME)
    #endif
    

    __attribute__((used))的作用是告訴編譯器,即使該變數或函式沒有被直接使用,也不要將其最佳化掉。這在一些特殊的情況下很有用,例如當你想要確保某個變數或函式在編譯後的可執行檔案中存在,即使它在程式碼中沒有被顯式呼叫或使用。這樣就確保了我們的foo函式不會被編譯器最佳化掉

    __attribute__((import_module("env"), import_name(#NAME)))是用於WebAssembly的特殊屬性,用於指定匯入函式所屬的模組和匯入函式的名稱。在WebAssembly中,可以從外部匯入函式,這些函式通常由宿主環境(如瀏覽器或wasmtime)提供。當你使用__attribute__((import_module("env"), import_name(#NAME)))屬性時,它告訴WebAssembly執行時,該函式屬於名為"env"的模組,並且其匯入名稱為#NAME

使用EM_IMPORT(add)宏告訴編譯器,這裡宣告的add方法其具體實現來自於其他模組,具體就是來自於env模組中的add函式。因此這裡宣告的add方法其實可以起任意的名字,只要簽名與env模組中的add方法相同即可。

編譯

使用如下命令進行編譯

# 在專案根目錄下
mkdir build
cd build

emcmake cmake ..
make

編譯後在build目錄下會生成toolbox.wasm二進位制檔案。

我們可以使用wasm2wat命令將編譯好的wasm二進位制檔案轉換成可讀的wat檔案來看一下生成的程式碼的結構

如果沒有安裝wasm2wat命令可以使用一下命令來安裝

sudo apt install wabt

執行wasm2wat toolbox.wasm -o toolbox.wat命令後,可以開啟toolbox.wat檔案檢視其結構如下

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (type (;1;) (func))
  (type (;2;) (func (param i32) (result i32)))
  (import "env" "add" (func (;0;) (type 0)))
  (func (;1;) (type 1))
  (func (;2;) (type 2) (param i32) (result i32)
    i32.const 1
    local.get 0
    call 0)
  (export "__wasm_call_ctors" (func 1))
  (export "__wasm_apply_data_relocs" (func 1))
  (export "foo" (func 2)))

可以看出,程式碼中import "env" "add"表示add函式來自env moduleadd函式。同時export "foo"表示toolbox.wasm對外暴露了foo函式。

wasmtime專案

wasmtime專案可以使用wasmtime支援的各種語言實現,這裡我們以C++為例,看看如何將前面兩個專案生成的.wasm檔案呼叫起來。

建立一個專案叫做demo-run,使用cmake進行專案構建,其目錄結構如下

demo-run
├── CMakeLists.txt
└── main.cpp

cmake配置

wasmtime專案可以使用gcc工具鏈進行編譯,因此它的CMakeLists.txt可以正常進行配置

cmake_minimum_required(VERSION 3.26)
project(demo_run)

set(CMAKE_CXX_STANDARD 17)

add_executable(demo_run main.cpp)
target_link_libraries(demo_run PUBLIC wasmtime)

因為我們需要在程式碼中使用wasmtime的庫,因此這裡需要使用target_link_libraries(demo_run PUBLIC wasmtime)wasmtime連結進來。這也就要求必須先按照第2節中的安裝方式配置好wasmtime的環境變數。

程式碼實現

具體wasmtime提供的每個API的用法在這裡不多做贅述,具體可以參考wasmtime官方檔案和官方提供的examples

#include <iostream>
#include <wasmtime.hh>
#include <fstream>

using namespace wasmtime;


std::vector<unsigned char> readFile(const char *name) {
    std::ifstream watFile(name, std::ios::binary);
    std::vector<unsigned char> arr;
    char byte;
    while (watFile.get(byte)) {
        arr.push_back(byte);
    }
    return arr;
}

int main() {
    std::cout << "Compiling module" << std::endl;
    Engine engine;

    // 載入calc.wasm成為module
    auto calcByteArr = readFile("calc.wasm");
    Span<uint8_t> calcSpan(calcByteArr.data(), calcByteArr.size());
    auto calcModule = Module::compile(engine, calcSpan).unwrap();

    // 載入toolbox.wasm成為module
    auto toolboxByteArr = readFile("toolbox.wasm");
    Span<uint8_t> toolboxSpan(toolboxByteArr.data(), toolboxByteArr.size());
    auto toolboxModule = Module::compile(engine, toolboxSpan).unwrap();

    std::cout << "Initializing..." << std::endl;
    Store store(engine);
    store.context().set_wasi(WasiConfig()).unwrap();

    std::cout << "Linking..." << std::endl;
    Linker linker(engine);
    linker.define_wasi().unwrap();

    // 連結器初始化calc module,例項化成具體的Instance
    auto calcInst = linker.instantiate(store, calcModule).unwrap();

    // 將上一步的calcInst中的所有export的物件定義到env module名下
    linker.define_instance(store, "env", calcInst).unwrap();

    // 連結器初始化toolbox module,例項化成具體的Instance
    auto toolboxInst = linker.instantiate(store, toolboxModule).unwrap();

    // 獲取toolboxInst中的foo方法
    auto func = std::get<Func>(toolboxInst.get(store, "foo").value());

    // 呼叫foo方法,傳入引數7,
    auto fooRes = func.call(store, {7}).unwrap();

    // 列印結果 FooResult: 8
    std::cout << "FooResult: " << fooRes[0].i32() << std::endl;

    return 0;
}

就像註釋中寫的那樣,我們將calc.wasmexport的方法add新增到了名稱為envmodule下,這樣上一步中C++編譯成的.wasm程式碼就可以連結到這個add方法。

編譯與執行

mkdir build
cd build
cmake ..
make

執行編譯後會生成可執行檔案demo_run,由於程式碼還要依賴兩個.wasm檔案,因此我們這裡手動將前面兩個專案生成的.wasm檔案複製到demo_run可執行檔案的同級目錄下

執行生成的demo_run可執行檔案後可得如下輸出

> ./demo_run

Compiling module
Initializing...
Linking...
FooResult: 8

以上就實現了C++和Rust透過wasmtime實現相互呼叫的過程。

相關文章