How to pass repr(C) struct with webassembly

I want to pass binary data between rust and c/webassembly.

use std::ffi::c_void;
use std::ptr;

#[repr(C)]
pub struct Buffer {
    ptr: *const c_void,
    len: usize,
}

#[link(wasm_import_module = "demo")]
extern "system" {
    #[link_name = "test"]
    fn test() -> Buffer;

    #[link_name = "test2"]
    fn test2(_: Buffer);

    #[link_name = "test3"]
    fn test3(_: i32) -> i32;
}


#[repr(C)]
pub struct Bridge {
    pub test: unsafe extern "system" fn() -> Buffer,
    pub test2: unsafe extern "system" fn(_: Buffer),
    pub test3: unsafe extern "system" fn(_: i32) -> i32,
}


#[cfg(target_arch = "wasm32")]
pub fn init_bridge(_: *const c_void) -> Bridge {
    Bridge {
        test,
        test2,
        test3,
    }
}


#[cfg(not(target_arch = "wasm32"))]
fn init_bridge(ptr: *const c_void) -> Box<Bridge> {
    let size = std::mem::size_of::<Bridge>();
    let mut data = vec![0u8; size];
    let target = data.as_mut_ptr();
    std::mem::forget(data);
    unsafe {
        std::ptr::copy(ptr as _, target, size);
        Box::from_raw(target as *mut Bridge)
    }
}

#[no_mangle]
extern "system" fn run() {
    init_bridge(ptr::null());
}

This code works fine in native, but in wasm the imported function type is not right.

import - ImportType { name: ImportName { module: "demo", field: "test" }, ty: Func(FuncType { params: [I32], results: [] }) }
import - ImportType { name: ImportName { module: "demo", field: "test2" }, ty: Func(FuncType { params: [I32], results: [] }) }
import - ImportType { name: ImportName { module: "demo", field: "test3" }, ty: Func(FuncType { params: [I32], results: [I32] }) }

My program needs some injection functions to work. And the program can be run in native or webassembly with the same code and just difference target to compile.

I wonder how to pass repr(C) struct with webassembly? Thanks

The imported function types are technically not wrong, although they might be unexpected. According to the C ABI structs are returned by writing to a caller-provided pointer, and on WASM (which is a 32 bit platform) pointers are represented as I32s. This ends up being the parameter you see in your first two functions (Edit: in the second function this is still a pointer but of course not for returning the value but for reading it from).

Rust also has an unstable wasm ABI that matches the ABI specified in the WebAssembly specification and uses multivalue returns. With this you should see test have signature () -> (i32, i32) and test2 have signature (i32, i32) -> (). https://rust.godbolt.org/z/Tn41EYhPh`

Also note that WASM has a separate memory than your executable (differently from a native shared library which instead does), so you can't pass native pointers to WASM and expect them to work. If you want to pass something to WASM by pointer you'll have to write it somewhere in the WASM memory and then pass a pointer (according to WASM, it would be an index for the host) to the WASM module.

Thanks very much, so the i32 is the stack pointer, so I can use it in my wrap function.