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主要是為了測試方便。
-
在shell中執行如下命令
curl https://wasmtime.dev/install.sh -sSf | bash
-
wasmtime的可執行檔案會被安裝在
${HOME}/.wasmtime
目錄下 -
執行以上命令後會在
${HOME}/.bashrc
或${HOME}/.bash_profile
檔案中幫我們新增以下環境變數export WASMTIME_HOME="${HOME}/.wasmtime" export PATH="$WASMTIME_HOME/bin:$PATH"
-
如果希望所有使用者(包括root)可以使用
wasmtime
命令,可以將以上環境變數設定到/etc/profile.d
中,我們可以在該目錄下建立wasmtime.sh
檔案,並新增一下程式碼export WASMTIME_HOME=/home/<xxx>/.wasmtime # 將xxx替換成自己的home目錄 export PATH="$WASMTIME_HOME/bin:$PATH"
-
可以使用如下命令直接執行
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。
-
可以在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
-
解壓以上檔案並將其移動到
/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
-
在
/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
-
下載wasmtime-cpp專案的include/wasmtime.hh檔案,將其放到
wasmtime.h
所在的目錄下,按照我們的安裝步驟,需要放置到/usr/local/wasmtime/include
目錄下 -
如此就可以在我們的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
wasm32-unknown-emscripten
:這個target是為了在Emscripten工具鏈下編譯Wasm。Emscripten是一個將C/C++程式碼編譯為Wasm和JavaScript的工具鏈。使用這個target,你可以在瀏覽器環境中執行編譯後的Wasm程式碼。wasm32-unknown-unknown
:這個target是為了在沒有任何作業系統支援的情況下執行WebAssembly程式碼而設計的。這種情況下,WebAssembly程式碼將執行在一個“裸機”環境中,沒有任何作業系統提供的支援。因此,如果你需要在裸機環境中執行WebAssembly程式碼,那麼使用這個target是一個不錯的選擇。wasm32-wasi
:這個target是為了在WebAssembly System Interface (WASI)上執行WebAssembly程式碼而設計的。WASI是一個標準介面,它提供了一些作業系統級別的功能,如檔案系統和網路訪問等。因此,如果你需要在WebAssembly中訪問這些作業系統級別的功能,那麼使用這個target是一個不錯的選擇。
由於我們不需要在Web環境中執行Rust程式碼,因此我們選擇安裝wasm32-unknown-unknown
和wasm32-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二進位制檔案的使用方法。
- 簡單使用
emcc -O3 hello.cpp -o hello.wasm
當我們將輸出目標的字尾名指定為wasm
時,編譯器會自動幫我們設定如下連線選項,上面的命令與下面的命令時等價的
emcc -O3 hello.cpp -o hello.wasm -s STANDALONE_WASM
這樣編譯出來的結果不會包含js
檔案,只會包含一個可被wasmtime
執行的wasm
檔案。
- 結合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++程式碼中的函式。
- Rust專案:包含一個
add
函式,做兩個整數的加法並返回結果,可以被外部呼叫。需要編譯成wasm。 - C++專案:包含一個
foo
函式,呼叫Rust中的add
函式並返回結果。需要編譯成wasm。 - 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 ()
這裡有幾點需要注意的
-
在使用emscripten時,我們使用
add_executable
指定編譯目標為可執行檔案,這是因為wasm本身是可執行的二進位製程式碼,在沒有特殊配置時,編譯後的wasm程式碼中會生成一個_start
函式,這個函式就是執行時執行wasm程式碼的入口。這裡如果我們將add_executable
替換成add_library
,則使用emscripten編譯後只會生成libtoolbox.a
庫檔案,而不會生成wasm程式碼。 -
針對emscripten編譯工具鏈,我們配置了編譯引數和連結引數
-
-Os
表示開啟編譯最佳化 -
-s SIDE_MODULE=1
表示將toolbox
編譯成module
,這樣生成的wasm就類似動態連結庫,可以讓wasmtime在執行時動態連結這份wasm程式碼。emscripten支援將程式碼編譯成兩種不同的
module
- Main modules:系統庫會被連結進去
- 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_KEEPALIVE
和EM_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 module
的add
函式。同時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.wasm
中export
的方法add
新增到了名稱為env
的module
下,這樣上一步中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實現相互呼叫的過程。