💨
Tauri アプリケーションで Wasmtime を使用して Wasm をロードするプラグインシステムを自作する
Tauri アプリケーションに Wasmtime を組み込み、Wasm モジュールを動的にロードして実行するプラグインシステムを作成する方法を解説します。
Tauri と Wasmtime のセットアップ
1. 新規 Tauri プロジェクトを作成
まず、cargo create-tauri-app
コマンドまたは Tauri CLI を使用して、新しい Tauri プロジェクトを作成します。
cargo install tauri-cli
cargo tauri dev
2. Wasmtime を Tauri アプリの依存関係に追加
次に、Cargo.toml
ファイルに Wasmtime を追加します。
cd src-tauri
cargo add wasmtime
プラグインシステムの実装
src-tauri/main.rs
)に関数を定義
1. Tauri アプリのメインファイル(プラグイン WASM モジュールをロードして実行するための関数を定義します。
このファイルでは、Tauri アプリケーションのメインエントリーポイントと、WASM プラグインをロードして実行するための関数が定義されています。
例として、greetコマンドをWasmプラグイン化してメッセージをWasmモジュール側から返すようにしています。
greetコマンドが呼び出されたらplugins ディレクトリにあるすべての WASM ファイルをスキャンし、最初に見つかったファイルをロードして実行します。
src-tauri/main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::path::Path;
use wasmtime::{Config, Engine, Linker, Module, Store};
fn load_plugin(path: &str, name: &str) -> Result<String, Box<dyn std::error::Error>> {
let engine = Engine::new(&Config::new())?;
let module = Module::from_file(&engine, Path::new(path))?;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker.module(&mut store, "", &module)?;
let instance = linker.instantiate(&mut store, &module)?;
let memory = instance.get_memory(&mut store, "memory").expect("failed to find memory");
let name_bytes = name.as_bytes();
memory.data_mut(&mut store)[..name_bytes.len()].copy_from_slice(name_bytes);
let main = instance.get_typed_func::<(i32, i32), i32>(&mut store, "_start")?;
let result_ptr = main.call(&mut store, (0, name_bytes.len() as i32))?;
let greeting = memory.data(&store)[result_ptr as usize..]
.iter()
.take_while(|&&c| c != 0)
.cloned()
.collect::<Vec<_>>();
let greeting_str = String::from_utf8(greeting)?;
println!("Plugin returned: {}", greeting_str);
Ok(greeting_str)
}
#[tauri::command]
fn greet(app_handle: tauri::AppHandle, name: &str) -> String {
println!("Setting up...");
if let Some(app_data_dir) = app_handle.path_resolver().app_data_dir() {
let dir = app_data_dir.join("plugins");
println!("Loading plugins from: {:?}", dir);
std::fs::create_dir_all(&dir).expect("Failed to create plugins directory");
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
println!("Loading plugin: {:?}", path);
if path.extension().unwrap_or_default() == "wasm" {
return load_plugin(path.to_str().unwrap(), name).unwrap_or_else(|_| "Failed to load plugin".to_string());
}
}
}
"No plugins found".to_string()
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
プラグインの作成とロード
1. Rust (または WASM にコンパイル可能な言語) でプラグインを作成し、WASM モジュールにコンパイル
まず、新しい Rust ライブラリプロジェクトを作成し、WASM モジュールにコンパイルします。
cargo new --lib my_wasm_plugin
cd my_wasm_plugin
次に、Cargo.toml
ファイルに以下の設定を追加します。
Cargo.toml
[lib]
crate-type = ["cdylib"]
そして、src/lib.rs
ファイルに以下のコードを追加します。
src/lib.rs
#[no_mangle]
pub extern "C" fn _start(name_ptr: *const u8, name_len: usize) -> *const u8 {
let name = unsafe { std::slice::from_raw_parts(name_ptr, name_len) };
let name_str = std::str::from_utf8(name).unwrap_or("stranger");
let greeting = format!("Hello, {}!", name_str);
let greeting_ptr = greeting.as_ptr();
std::mem::forget(greeting); // Prevent Rust from deallocating the string
greeting_ptr
}
次に、ターゲットを追加し、WASM モジュールをビルドします。
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown
plugins
ディレクトリに配置
2. コンパイルされたプラグイン WASM ファイルを ビルドされた WASM ファイルを plugins
ディレクトリにコピーします。
cp target/wasm32-unknown-unknown/release/my_wasm_plugin.wasm ~/Library/Application\ Support/com.example.dev/plugins/
3. Tauri アプリを実行すると、プラグインが自動的にロードされ、実行
最後に、Tauri アプリを実行します。プラグインが自動的にロードされ、実行されます。
tauri dev
実行結果の例:
Loading plugins from: "/Users/laiso/Library/Application Support/com.example.dev/plugins"
Loading plugin: "/Users/laiso/Library/Application Support/com.example.dev/plugins/my_wasm_plugin.wasm"
Plugin returned: Hello, user!
Discussion