WebAssembly實踐指南——C++和Rust通過wasmtime實現相互調用實例

来源:https://www.cnblogs.com/leometeor/archive/2023/09/19/17715779.html
-Advertisement-
Play Games

C++和Rust通過wasmtime實現相互調用實例 1 wasmtime介紹 wasmtime是一個可以運行WebAssembly代碼的運行時環境。 WebAssembly是一種可移植的二進位指令集格式,其本身與平臺無關,類似於Java的class文件位元組碼。 WebAssembly本來的設計初衷 ...


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實現相互調用的過程。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天我就介紹一個可以改變你這種現狀的技術:DAO。但在介紹DAO之前我們需要先瞭解一下所有人都在熱烈討論的web3.0。 ...
  • 設計一個具有創意性的物聯網系統需要充分發揮創新思維,考慮系統的實用性、可靠性、成本效益和可擴展性。以下是一些設計建議: ...
  • 一、定義 將一個類的介面轉換成客戶希望的另一個介面。適配器模式讓那些介面不相容的類可以一起工作。適配器模式是一種結構型模式。 二、描述 包含以下三個角色: 1、Target(目標抽象類):目標抽象類定義了客戶所需要的介面,可以是一個抽象類或介面,也可以是一個具體的類,由於C#不支持多繼承,所以它只能 ...
  • 前言 個人網站使用Vue作為前端,SpringBoot作為後端,MySQL作為資料庫,但前端每次請求都會從MySQL資料庫中讀取數據,而MySQL資料庫的數據是存儲於伺服器磁碟中,所以響應速度有一定影響。之前瞭解過一點Redis資料庫,該資料庫數據存儲於記憶體中(也可以持久化於磁碟中),數據讀取速度就 ...
  • 早上看到一篇關於Spring Boot虛擬線程和Webflux性能對比的文章,覺得還不錯。內容較長,我就不翻譯了,抓重點給大家介紹一下這篇文章的核心內容,方便大家快速閱讀。 測試場景 作者採用了一個儘可能貼近現實操作的場景: 從授權頭信息中提取JWT 驗證JWT並從中提取用戶的Email 使用用戶的 ...
  • 你可能會為不同版本的工具鏈而煩惱,就算是 ruster 也一樣。介紹一個非常好用的管理工具給大伙,就是使用 Rustup 安裝和更新 Rust 工具鏈。安裝 Rustup 之後,可以在 stable, beta,和 nightly 等版本的 Rust 編譯器工具之間無縫切換。 ...
  • MyBatis中#{}和${}的用法 說一下為什麼要寫這篇文章,最近面試有被問到,一下子想不出來有啥區別,想記錄一下加深自己的理解,同時自己也經常用MyBatis-Plus忽略了XML文件的編寫和使用,所以需要加深一下這塊的知識 一、例子 1、#{}將傳入的數據當作一個字元串,會對傳入的數據加上一個 ...
  • Caffeine Cache以其高性能和可擴展性贏得 本地緩存之王 的稱號,它是一個Java緩存庫。它的設計目標是優化計算速度、記憶體效率和實用性,以符合現代軟體開發者的需求。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...