冬休みをまるまる2日使ってやっとできました。難しかった。。。
マニアックなことに手を付けてしまったようで、検索してもあまりヒットせず。最終的には公式のマニュアル(WASI, wasmtime)のサンプルを見ながらやることになりました。wasi を使っているのは、WASMコンポーネントから println を使いたかっただけなのですが、だけなのですが、、、難しかったー![]()
host, guest を作ってみる
Host が実行ファイル。Guest が WASM コンポーネント(.wasm ファイル)です。WIT を使って設計を共有したかったのですが、それだけのことでこんなに時間がかかろうとは。
目標
- host 側で guest の hello を呼ぶ
- guest で hello world する
- guest から host の message() をコール
setup
cargo component new myguest --lib
cargo component new myhost
myguest は、cargo component build で wasm を作成、myhost 側は cargo run で動かせます。cargo component build は --target wasm32-wasip2 を指定しないと p1 で作成すので注意です。
wit を作る
myhost/wit/myworld.wit
package component:my-example;
interface host-api {
message: func(msg: string);
}
world my-world {
// GuestがHostからインポートする(Hostの関数を呼ぶ)
import host-api;
// GuestがHostへエクスポートする(Hostから呼ばれる)
export hello: func();
}
Guest
path を指定して host 側の WIT を共有しいます。cargo component build --target wasm32-wasip2 --release で wasm ファイルを作ります。
myguest/Cargo.toml
[package]
name = "myguest"
version = "0.1.0"
edition = "2024"
[dependencies]
wit-bindgen-rt = { version = "0.44.0", features = ["bitflags"] }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "component:myguest"
[package.metadata.component.dependencies]
[package.metadata.component.target]
path = "../myhost/wit/myworld.wit"
myguest/src/lib.rs
#[allow(warnings)]
mod bindings;
use bindings::Guest;
use bindings::component::my_example::host_api;
struct Component;
impl Guest for Component {
fn hello() {
// 1. Guestでhello world
println!("Guest: Hello world!");
// 2. GuestからHostのmessage()をコール
host_api::message("Hello from Guest via host-api!");
}
}
bindings::export!(Component with_types_in bindings);
Host
ここが本当にめちゃくちゃ苦労しました。
myhost/src/main.rs
[package]
name = "myhost"
version = "0.1.0"
edition = "2024"
[package.metadata.component]
package = "component:myhost"
[package.metadata.component.target.dependencies]
[dependencies]
wasmtime = { version = "40.0.0", features = ['component-model'] }
wasmtime-wasi = "40.0.0"
wit-bindgen-rt = { version = "0.44.0", features = ["bitflags"] }
myhost/src/main.rs
use wasmtime::component::*;
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
// バインディングの生成 MyWorld trait はここで作られる
// world 名を指定すると wit ディレクトリから自動で使用する world を選びます
bindgen!("my-world");
struct State {
ctx: WasiCtx,
table: ResourceTable,
}
impl WasiView for State {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
}
}
// Host側で提供する関数の実装
// <パッケージ名>::<interface名>::Host
impl component::my_example::host_api::Host for State {
fn message(&mut self, msg: String) {
println!("Host received message: {}", msg);
}
}
fn main() -> wasmtime::Result<()> {
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
let component = Component::from_file(
&engine,
"../myguest/target/wasm32-wasip2/release/myguest.wasm")?;
let mut linker = Linker::<State>::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
MyWorld::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state)?;
let mut builder = WasiCtx::builder();
builder.inherit_stdout(); // Guest で println! を使うため
builder.inherit_stderr();
let mut store = Store::new(
&engine,
State {
ctx: builder.build(),
table: ResourceTable::new(),
},
);
let bindings = MyWorld::instantiate(&mut store, &component, &linker)?;
// 3. Host側でguestのhelloを呼ぶ
bindings.call_hello(&mut store)?;
Ok(())
}
まだ ResourceTable 周りがよくわかってませんが、あとはコピペで作っていける気がしてますが非同期対応はさせないとですね。