It really took me days to solve this problem. I hope it helps!
Since it's a lot of info to pack here. I will try to keep it short but If you want to know more let me know and I'll expand on my answer.
Short explanation of why this is happening
This actually happens because wasm by default does not return Strings
so the smart people at wasm-bindgen
did something so when you run wasm-pack build
it generates a js code that do this for you. The function hello
does not return a string
, instead returns a pointer. To proof this, you can check the files generated when you build the imported_lib.rs
You can see that it generates the file imported_lib.wasm.d.ts
that looks something like this:
export const memory: WebAssembly.Memory;
export function add(a: number, b: number): number;
export function hello(a: number, b: number, c: number): void;
export function popo(a: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export function __wbindgen_free(a: number, b: number): void;
- You can see that the function
add
does match how you declared, 2 parameters and returns a number
. In the other hand you can see that the function hello
takes 3 parameters and return a void
(very different to how you declared)
- You can also see that the command
wasp-pack build
generated some extra functions like (__wbindgen_add_to_stack_pointer
, __wbindgen_free
, etc). With these functions they are able to get the string.
The other file that the command wasm-pack build
generates is imported_lib_bg.js
. In this file you can see that they export the function hello
. Here it's where JavaScript call the compiled wasm function and "translate" the pointer to the actual string.
So basically you would have to do something similar to what it is in the file imported_lib_bg.js
. This is how I did it:
Solution
In your main project create a folder call js
, and inside that folder create a file call getString.js
. Your project filesystem should look something like this:
mainProject
├── js
├── getString.js
├── src
├── main_lib.rs
├── ...
├── www
├── ...
And the file should have this:
function getInt32Memory0(wasm_memory_buffer) {
let cachedInt32Memory0 = new Int32Array(wasm_memory_buffer);
return cachedInt32Memory0;
}
function getStringFromWasm(ptr, len, wasm_memory_buffer) {
const mem = new Uint8Array(wasm_memory_buffer);
const slice = mem.slice(ptr, ptr + len);
const ret = new TextDecoder('utf-8').decode(slice);
return ret;
}
let WASM_VECTOR_LEN = 0;
function getUint8Memory0(wasm_memory_buffer) {
let cachedUint8Memory0 = new Uint8Array(wasm_memory_buffer);
return cachedUint8Memory0;
}
const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder;
let cachedTextEncoder = new lTextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc, wasm_memory_buffer) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0(wasm_memory_buffer).subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0(wasm_memory_buffer);
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0(wasm_memory_buffer).subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
/**
* @param {&JsValue} wasm: wasm object
* @param {string} fn_name: function's name to call in the wasm object
* @param {string} name: param to give to fn_name
* @returns {string}
*/
export function getString(wasm, fn_name, name) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc, wasm.memory.buffer);
const len0 = WASM_VECTOR_LEN;
//wasm.hello(retptr, ptr0, len0);
wasm[fn_name](retptr, ptr0, len0);
var r0 = getInt32Memory0(wasm.memory.buffer)[retptr / 4 + 0];
var r1 = getInt32Memory0(wasm.memory.buffer)[retptr / 4 + 1];
return getStringFromWasm(r0, r1, wasm.memory.buffer);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
}
}
In your main_lib.rs
add this:
...
#[wasm_bindgen(module = "/js/getStrings.js")]
extern "C" {
fn getString(wasm: &JsValue, nf_name: &str, name: &str) -> String;
}
...
let hello_out= getString(c.as_ref(), &"hello", "Arnold");
console_log!("# hello returns: {:?}", hello_out);
...
That should totally work!
format_args!($($t)*).to_string()
? Wow, that is impressive. Just useformat!($($t)*)
. – Suziesuzukiwasm-bindgen
you should stick to FFI-safe code, just as if you were interfacing with C. That is, a#[no_mangle] pub unsafe extern "C" hello(name: *const u8, reply: *mut u8, reply_len: usize)
or something like that. – Formulaicimported_lib
usingjs-sys
. If you look into the corresponding.wasm.d.ts
file you can see that the actual signature of hello is as follows:export function hello(a: number, b: number, c: number): void;
. I was getting started with rewriting all the glue code but this turns out to be quite painful. – Shoreward