3
votes

I need to write a function that returns array of u16 integers in Rust. This function then should be used by FFI.

extern crate libc;
use libc::{uint16_t};

#[no_mangle]
pub extern fn ffi_test() ->  *const uint16_t {
    let test: [u16;4] = [1,2,3,4];

    test.as_ptr()
}

Rust code compiles without errors. I used Ruby to test the ffi call:

# coding: utf-8
require 'ffi'

module MyMod
  extend FFI::Library
  ffi_lib 'my_ffi_test_lib'
  attach_function :ffi_test, [], :pointer
end

a_ptr = MyMod.ffi_test
size = 4
result_array = a_ptr.read_array_of_uint16(size)
p result_array

But the results are totally wrong (expected: [1, 2, 3, 4]):

$ ruby ffi_test.rb
[57871, 25191, 32767, 0]

As if I am reading totally diffirent memory addr. I assume maybe that I should not use #as_ptr() on Rust array?

EDIT

As per recommendation of @FrenchBoiethios I tried to box the array:

extern crate libc;
use libc::{uint16_t};

#[no_mangle]
pub extern fn ffi_test() ->  *mut uint16_t {
    let test: [u16;4] = [1,2,3,4];

    let b = Box::new(test);
    Box::into_raw(b)
}

This gives compile error:

note: expected type `std::boxed::Box<u16>`
         found type `std::boxed::Box<[u16; 4]>`
2
Your array is on the stack. You must use a vector or box it, otherwise, you're having a lifetime issue.Boiethios
@FrenchBoiethios thanks for the comment. I updated the question. Any hint how could I fix this?Ernest

2 Answers

4
votes

Your array is on the stack, so there is a lifetime issue when you returns it as a pointer (returned pointer to a local variable). You must allocate it in the heap:

#[no_mangle]
pub extern "C" fn ffi_test() -> *mut u16 {
    let mut test = vec![1, 2, 3, 4];
    let ptr = test.as_mut_ptr();
    
    std::mem::forget(test); // so that it is not destructed at the end of the scope
    
    ptr
}

or

#[no_mangle]
pub extern "C" fn ffi_test() -> *mut u16 {
    let test = Box::new([1u16, 2, 3, 4]);  // type must be explicit here...

    Box::into_raw(test) as *mut _          // ... because this cast can convert
                                           // *mut [i32; 4] to *mut u16
}
1
votes

I am trying to learn Rust ffi, those implementations are a frankenstein creation from different sources in internet. So take it with a grain of salt.

Currently I am with two approaches:

a) Remove the array from rust GC and return the point. User need to promise to call free later.

#[repr(C)]
pub struct V2 {
    pub x: i32,
    pub y: i32,
}

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

#[no_mangle]
extern "C" fn generate_data() -> Buffer {
    let mut buf = vec![V2 { x: 1, y: 0 }, V2 { x: 2, y: 0}].into_boxed_slice();
    let data = buf.as_mut_ptr();
    let len = buf.len() as i32;
    std::mem::forget(buf);
    Buffer { len, data }
}

#[no_mangle]
extern "C" fn free_buf(buf: Buffer) {
    let s = unsafe { std::slice::from_raw_parts_mut(buf.data, buf.len as usize) };
    let s = s.as_mut_ptr();
    unsafe {
        Box::from_raw(s);
    }
}

b) Send the array through FFI callback function. User need to promise to not keep references, but dont need to call free.

#[no_mangle]
pub extern "C" fn context_get_byte_responses(callback: extern "stdcall" fn (*mut u8, i32)) -> bool {
    let bytes: Vec<u8> = vec![];
    callback(bytes.as_mut_ptr(), bytes.len() as i32);
    true
}