💨

Tauri アプリケーションで Wasmtime を使用して Wasm をロードするプラグインシステムを自作する

2024/09/16に公開

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

プラグインシステムの実装

1. Tauri アプリのメインファイル(src-tauri/main.rs)に関数を定義

プラグイン 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

2. コンパイルされたプラグイン WASM ファイルを plugins ディレクトリに配置

ビルドされた 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