Reputation: 133
At a high level I am trying to create a Rust host program that loads a WASM module at runtime using Wasmtime and calls a WASM function that returns a string. I can get this working with numeric types such as usize
, but can't work out how to handle strings (or other complex types).
In my plugin crate I have a lib.rs
that I compile with cargo build --target=wasm32-unknown-unknown --release
:
use std::ffi::CString;
use std::os::raw::c_char;
static PLUGIN_NAME: &'static str = "Test Plugin";
#[no_mangle]
pub extern "C" fn plugin_name() -> *mut c_char {
let s = CString::new(PLUGIN_NAME).unwrap();
s.into_raw()
}
#[no_mangle]
pub fn plugin_name_len() -> usize {
PLUGIN_NAME.len()
}
This is based on code in one of the answers to this question which is close to what I'm looking for, but uses JavaScript on the host side.
In my host crate I have a main.rs
:
use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;
fn main() -> anyhow::Result<()> {
let engine = Engine::default();
let Some(file) = std::env::args().nth(1) else {
anyhow::bail!("USAGE: host <WASM FILE>");
};
let module = Module::from_file(&engine, file)?;
let linker = Linker::new(&engine);
let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_args()
.expect("should always be able to inherit args")
.build();
let mut store = Store::new(&engine, wasi);
let instance = linker.instantiate(&mut store, &module)?;
let Ok(plugin_name_len) = instance.get_typed_func::<(), u32>(&mut store, "plugin_name_len") else {
anyhow::bail!("Failed to get plugin_name_len");
};
let len = plugin_name_len.call(&mut store, ())?;
println!("Length: {}", len);
let Ok(plugin_name) = instance.get_typed_func::<(), &str>(&mut store, "plugin_name") else {
anyhow::bail!("Failed to get plugin_name");
};
Ok(())
}
But that doesn't compile as you can't use &str
in a call to instance.get_typed_func()
Could someone share an example (and an explanation) of how to call a WASM function that returns a string from a Rust Wasmtime host program?
Upvotes: 3
Views: 2452
Reputation: 133
I've got this working.
The TL;DR is that the plugin WASM function plugin_name()
returns a 32-bit integer which is effectively a pointer into the WASM memory. To access the WASM memory you need to get a Wasmtime Memory
struct by accessing the "memory"
export from your Instance
. You can then use the 32-bit integer you got from your call to plugin_name()
as the offset to the first character of your string and this offset + the length of your string as the last character. Convert this slice of u8
s into a Vec and feed it into String::from_utf8()
and you've got a String
!
Updated, working, main.rs
:
use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;
fn main() -> anyhow::Result<()> {
let engine = Engine::default();
let Some(file) = std::env::args().nth(1) else {
anyhow::bail!("USAGE: host <WASM FILE>");
};
let module = Module::from_file(&engine, file)?;
let linker = Linker::new(&engine);
let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_args()
.expect("should always be able to inherit args")
.build();
let mut store = Store::new(&engine, wasi);
let instance = linker.instantiate(&mut store, &module)?;
let Ok(plugin_name_len) = instance.get_typed_func::<(), u32>(&mut store, "plugin_name_len") else {
anyhow::bail!("Failed to get plugin_name_len");
};
let len = plugin_name_len.call(&mut store, ())? as usize;
let Ok(plugin_name) = instance.get_typed_func::<(), u32>(&mut store, "plugin_name") else {
anyhow::bail!("Failed to get plugin_name");
};
let ptr = plugin_name.call(&mut store, ())? as usize;
let Some(memory) = instance.get_memory(&mut store, "memory") else {
anyhow::bail!("Failed to get WASM memory");
};
let data = memory.data(&store)[ptr..(ptr + len)].to_vec();
let name = String::from_utf8(data)?;
println!("Plugin name: {}", name);
Ok(())
}
Note - I'm converting len
and ptr
from u32
to usize
so that I can use them to index into the WASM memory from Rust.
Upvotes: 6