Closed12
Rustアプリにwasmerを埋め込む
- dioxusを使ってwebでもdesktopでも動くアプリを作りたい
- プラグインシステムを作りたい
- けどプラグインもRustで書きたい
- → wasm
- →wasmerにはwasmer-jsというものがあってどうやらwasm環境でも動かせる?
環境
- arch on wsl
- Rustはなるべくnightlyを使わないようにしたい
プロジェクト作成
構成はこんな感じにする
app
│ public
│ src
│ └ main.rs
│ Cargo.toml
└ Dioxus.toml
plugins
└ sample
│ src
│ └ lib.rs
└ Cargo.toml
Cargo.toml
(どうでもいいけどこういうときvimだとファイラーのテキスト直接コピーできて便利だな)
app以下はdioxus-cliで作る
$ dioxus create
ワークスペースのルートCargo.toml
root/Cargo.toml
[workspace]
resolver = "2"
members = ["app", "plugins/sample"]
ここでresolver = "2"
を指定するのが重要。こうしないとcfgでfeature出し分けができない(超ハマった)
appのCargo.toml
root/app/Cargo.toml
[package]
name = "<app name>"
version = "0.1.0"
edition = "2021"
[target.'cfg(target_arch = "wasm32")'.dependencies]
dioxus = { version = "0.1.8", features = ["web"] }
wasmer = { version = "2.0", features = [
"js-default",
], default-features = false }
wasm-logger = "0.2.0"
console_error_panic_hook = "0.1.7"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus = { version = "0.1.8", features = ["desktop"] }
wasmer = { version = "2.0" }
これでビルド時に切り替えられる
参考:
プラグイン側wasmを作る
- wasm-packを使うべき?
- それとも
wasm32-unknown-unknown
かwasm32-wasi
? - https://github.com/wasmerio/wasmer-rust-example/tree/master/wasm-sample-app
- lapceというエディタのプラグインが参考になりそう?
とりあえずこの記事を参考にして作る
unsafeだけどひとまず我慢
あれ?ワークスペースごとにターゲットの設定できるんだっけと思ったら案の定nightly限定機能みたいなのでしょうがないのでnightlyにする
workspace/rust-toolchain.toml
[toolchain]
channel = "nightly"
targets = ["wasm32-unknown-unknown"]
workspace/plugins/sample/Cargo.toml
cargo-features = ["per-package-target"]
[package]
name = "plugin-sample"
version = "0.1.0"
edition = "2021"
default-target = "wasm32-unknown-unknown"
[dependencies]
[lib]
crate-type = ["cdylib"]
とりあえずプラグイン側だけビルドしてwatを見てみる
$ cargo build -p plugin-sample
$ paru -S wabm # wasm2watコマンドを入れる
$ wasm2wat ./target/wasm32-unknown-unknown/debug/plugin_sample.wasm > plugin_sample.wat
結果
plugin_sample.wat
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func))
(import "env" "print_str" (func $print_str (type 0)))
(func $_ZN4core3str21_$LT$impl$u20$str$GT$3len17hd6f34eff50e5c25bE (type 1) (param i32 i32) (result i32)
(local i32 i32 i32 i32)
global.get 0
local.set 2
i32.const 32
local.set 3
local.get 2
local.get 3
i32.sub
local.set 4
local.get 4
local.get 0
i32.store offset=8
local.get 4
local.get 1
i32.store offset=12
local.get 4
local.get 0
i32.store offset=16
local.get 4
local.get 1
i32.store offset=20
local.get 4
local.get 0
i32.store offset=24
local.get 4
local.get 1
i32.store offset=28
local.get 4
i32.load offset=24
drop
local.get 4
i32.load offset=28
local.set 5
local.get 5
return)
(func $_ZN4core3str21_$LT$impl$u20$str$GT$6as_ptr17hc3437732d3c877b4E (type 1) (param i32 i32) (result i32)
(local i32 i32 i32)
global.get 0
local.set 2
i32.const 16
local.set 3
local.get 2
local.get 3
i32.sub
local.set 4
local.get 4
local.get 0
i32.store offset=8
local.get 4
local.get 1
i32.store offset=12
local.get 0
return)
(func $hello_wasm (type 2)
(local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
i32.const 0
local.set 0
local.get 0
i32.load offset=1048592
local.set 1
i32.const 0
local.set 2
local.get 2
i32.load offset=1048596
local.set 3
local.get 1
local.get 3
call $_ZN4core3str21_$LT$impl$u20$str$GT$6as_ptr17hc3437732d3c877b4E
local.set 4
i32.const 0
local.set 5
local.get 5
i32.load offset=1048592
local.set 6
i32.const 0
local.set 7
local.get 7
i32.load offset=1048596
local.set 8
local.get 6
local.get 8
call $_ZN4core3str21_$LT$impl$u20$str$GT$3len17hd6f34eff50e5c25bE
local.set 9
local.get 4
local.get 9
call $print_str
return)
(table (;0;) 1 1 funcref)
(memory (;0;) 17)
(global (;0;) (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048600))
(global (;2;) i32 (i32.const 1048608))
(export "memory" (memory 0))
(export "hello_wasm" (func $hello_wasm))
(export "__data_end" (global 1))
(export "__heap_base" (global 2))
(data (;0;) (i32.const 1048576) "Hello, World!\00\00\00\00\00\10\00\0d\00\00\00"))
普通のアセンブリは全然わかんないけどwatは割と見ただけでわかる感じ
よくわからないけどdioxusはスレッドセーフではなくてwasmからstateにアクセスできない?
Rust初心者すぎて何もわからないのでとりあえずdioxusとの連携はなかったことにする
数時間の格闘の末についにHello Worldの出力に成功した
wasmerは昔の情報しかなくてつらみがあった
$ cargo build -p plugin-sample
$ cargo run
workspace/app/src/main.rs
use dioxus::prelude::*;
use wasmer::*;
fn main() {
launch();
}
#[derive(WasmerEnv, Clone, Default)]
pub struct Env {
#[wasmer(export)]
memory: LazyInit<Memory>,
}
fn call_wasm() {
static wasm_bytes: &'static [u8] =
include_bytes!("../../target/wasm32-unknown-unknown/debug/plugin_sample.wasm");
let store = Store::default();
let module = Module::new(&store, wasm_bytes).unwrap();
fn print_str(env: &Env, ptr: u32, length: u32) {
println!("ptr:{}, length:{}", ptr, length);
let wasmptr: WasmPtr<u8, Array> = WasmPtr::new(ptr);
let str = wasmptr
.get_utf8_string(env.memory_ref().unwrap(), length)
.unwrap();
println!("{}", str);
}
let print_str_func = Function::new_native_with_env(&store, Env::default(), print_str);
let import_object = imports! {
"env" => {
"print_str" => print_str_func,
}
};
let instance = Instance::new(&module, &import_object).unwrap();
let hello = instance
.exports
.get_function("hello_wasm")
.unwrap()
.native::<(), ()>()
.unwrap();
hello.call();
}
#[cfg(target_arch = "wasm32")]
fn luanch() {
wasm_logger::init(wasm_logger::Config::default());
console_error_panic_hook::set_once();
dioxus::web::launch(app);
}
#[cfg(not(target_arch = "wasm32"))]
fn launch() {
dioxus::desktop::launch_cfg(app, |c| {
c.with_window(|w| {
w.with_resizable(true).with_inner_size(
dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0),
)
})
});
}
fn app(cx: Scope) -> Element {
let (text, set_text) = use_state(&cx, || "");
cx.render(rsx!(
button {onclick: |_|{call_wasm();} , "print" }
p { "{text}" }
))
}
こんな感じ
これを見るとzellijではメモリ操作みたいな面倒くさいことをせずにwasiの標準入出力でデータをやり取りしているみたい
こっちのほうが大分楽そう
このスクラップは2022/02/22にクローズされました