FFI and buffer pointers

I've implemented several functions that use simple types and strings but having trouble with this one. I have a C function:

void fexchange0 (int channel, double* in, double* out, int* error)

*in is the input data of 1024 doubles and *out is the transformed output data of 1024 doubles.
In essence my external C is

fn fexchange0(ch_id: i32, in_buf: &mut [f64; 1024], out_buf: &mut [f64; 1024], error: &mut i32);

and the function to call fexchange.

pub fn wdsp_exchange(ch_id: i32, in_buf: &mut [f64; 1024],  out_buf: &mut [f64; 1024], error: &mut i32 ) {
	unsafe{fexchange0(ch_id, in_buf, out_buf, error)}

The two arrays are pre-allocated. This gives STATUS_ACCESS_VIOLATION which wasn't unexpected. I know there has to be a lot more to this. I think I need to allocate the output buffer in some other way as a raw pointer or something.

I'm surprised this compiles. Normally you'd need to do in_buf.as_mut_ptr() to go from a &mut [f64; 1024] (which implicitly coerces to a *mut [f64; 1024]) to a pointer to its first element (*mut f64). I'm guessing you defined the extern "C" function as accepting a *mut [f64; 1024] instead of *mut f64?

It doesn't really make a difference in this case because a pointer to an array is the same as a pointer to its first element, but a more accurate translation would be something like this:

extern "C" {
  fn fexchange0(ch_id: i32, in_buf: *mut f64, out_buf: *mut f64, error: &mut i32);

pub fn wdsp_exchange(
  ch_id: i32, 
  in_buf: &mut [f64; 1024],  
  out_buf: &mut [f64; 1024], 
  error: &mut i32 ,
) {
    fexchange0(ch_id, in_buf.as_mut_ptr(), out_buf.as_mut_ptr(), error);

In a situation like this, there are a couple reasons you might get a segfault:

  • The fexchange0() signature you've declared in Rust isn't "compatible"[1] with the function defined in C.
    • The most common way this can happen is when you forget an argument or you are passing a struct by value and the Rust struct has "incompatible" fields with the C version.
    • If that happens, one side might try reading an argument and have a bad time because the argument was never provided and they've just read from some random memory location (e.g. C expects a 5th pointer parameter to be passed in via the RAX register, but Rust never sets that register so we reinterpret whatever was in there before as an address and try to do things with it)
  • Your 1024 length isn't correct so the C side ends up reading/writing past the end of one of the buffers, messing up something else in memory or segfaulting
  • I'm guessing ch_id is used as an index into some global data structure/array. If you pass in the wrong ch_id and the C code doesn't do any bounds checks, we could end up trying to do things with uninitialized memory
  • The C code has a bug completely unrelated from Rust

If you can, you might want to run this under a debugger so you can get a backtrace and see exactly which line/variable triggered the access violation.

The nice thing is that as long as the Rust code outside wdsp_exchange() is 100% safe, you know the problem is either at the point where C and Rust meet or in the C code itself.

  1. I'm going to use the term "compatible" a bit loosely here, but basically it means they've got the exact same representation in memory. ↩ī¸Ž

1 Like

One more...

  • Difference in calling convention. For example, Windows includes the stdcall calling convention. Mixing that with the C calling convention is guaranteed to fault if there are any parameters.
1 Like

Thanks. I was just about there with the changes but confirmation is nice. I still have a problem but its not Rust. I can read and write the arrays in the C function so the interface is good but I need to get the array sizes correct and then I should be good to go.

1 Like