What's the correct way to pass struct in FFI?

Usually, I pass the FFI struct in extern "system" fn directly.

#[repr(C)]
pub struct Buffer {
    data:*mut u8,
    len: i32,
}

extern "system" fn some_function(a:Buffer) -> Buffer {
   todo!()
}

But since the C ABI Changes for wasm32-unknown-unknown | Rust Blog, now I use it like this.

extern "system" fn some_function(a:&Buffer) ->Buffer {
  todo!()
}

It is fine when using it in Windows, but I got a crash when it runs on Android.

So what's the proper way to be compatible on all platforms?

Correct FFI always depends on what the code in the other language is doing, not just Rust. What is the C (or other language) function signature that you are trying to match with this Rust function?

I use rust with c#,and it is like this

[AOT.MonoPInvokeCallback(typeof(System.Func<Buffer, Buffer>))]
public static Buffer SomeFunction(Buffer b) {}

The changes described in that blog post don’t affect any other ABIs than the WASM one. You should continue to describe your Rust and C# ends to be as similar as possible, though to avoid nasty 32-bit surprises you should use use extern "C", I think.

The reason why 64-bit Windows continues to work is that it always passed that kind of structure as a pointer, but Android presumably uses the same SYSV ABI as Linux, and passes it in two registers. The latter doesn’t match &Buffer, leading to a crash.

This is bad news. I tested it on Windows, so I assume it works on Linux too.
I expected the ffi code to be shared in rust native and Rust WebAssembly.
Now it's nasty to keep the code shared.

I don't know C#, but can you not pass it by pointer from that side? Shouldn't that solve the problem, always passing by reference on all platforms?

Well, that's a solution, but by doing that, I should call a Marshal.PtrToStructure function, which I don't want to.

What would this look like in C or C++? If I do a trivial conversion from Rust to C++, from this Rust code:

#[repr(C)]
pub struct Buffer {
    data: *mut u8,
    len: i32,
}

pub unsafe extern "C" fn some_function(buffer: Buffer) -> Buffer {
    buffer
}

to this C++ code:

#include <cstdint>

typedef struct {
    unsigned char *data;
    int32_t len;
} Buffer;

extern "C" Buffer some_function(Buffer buffer) {
    return buffer;
}

the compilers generate effectively the same code, so for something this simple the Rust code looks correct.

You can use the ref keyword in c# to pass structs by reference.

Yes, this is what I do before. But wasm32-unknown-unknown will not accept this ABI declaration if I don't misunderstand it. I thought the &Buffer ABI declaration is a standard ABI declaration but apparently it's not. What magically is, when I revert my code to use Buffer not &Buffer, the compiler warning in wasm32-unknown-unknown did not appear, and everything works again. ^_^.

I am not sure if it is a good idea to pass ref in AOT in c#.

WASM will still accept extern "C" fn some_function(a: Buffer) -> Buffer as a function signature, in case you’re targeting 32-bit WASM with your library. However, the generated WASM bytecode has changed.

If you’re not seeing any issues building Rust and C# code for WASM, that blog post does not apply to you.

You are right. I try to remember what happened, and the compiler warns for a file which should not appear in wasm. I misunderstand it with the case in the post. And later I exclude the file from wasm32, that's why when I revert the code to Buffer, there is not any issue either.