FFI allocating and returning array of c strings

Hi,

I am trying to implement a function in Rust that is called from C and that returns an array of null terminated strings. The array must be allocated by Rust and must remain valid until the rust library is called the next time.

I am aware of the methods for converting between rust and c strings, but I can't quite wrap my head around doing this for an array/vector. Are there any build in functions for doing this, or do I have to roll out my own solution? If so can anyone guide me in the right direction?

Any help is much appreciated :slight_smile:

You will need to turn your vector into a pointer and ensure it's not dropped.
See https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d0e44ce1f765ce89523ef89ccd864e54

1 Like

Using safer_ffi:

use ::safer_ffi::prelude::*;

/// Some doc.
///
/// \remark the obtained `Vec` must be freed using `free_rust_vec`.
#[ffi_export]
fn foo () -> repr_c::Vec<char_p::Box>
{
    let mut vec: Vec<char_p::Box> = vec![];
    vec.push(
        char_p::new("Hello, World!") // Panics (and aborts) if inner null byte.
    );
    vec.into() // Transform the Rust `Vec` into a `#[repr(C)]` one.
}

/// Frees a single Rust-allocated C string.
#[ffi_export]
fn free_rust_string (string: char_p::Box)
{
    drop(string);
}

/// Frees the input `Vec`,
/// which recursively calls `free_rust_string` on its first `.len` elements.
#[ffi_export]
fn free_rust_vec (vec: repr_c::Vec<char_p::Box>)
{
    drop(vec);
}

which will auto-generate the following header definition:

/** \brief
 *  Same as [`Vec<T>`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout
 */
typedef struct {

    char * * ptr;

    size_t len;

    size_t cap;

} Vec_char_ptr_t;

/** \brief
 *  Some doc.
 * 
 *  \remark the obtained `Vec` must be freed using `free_rust_vec`.
 */
Vec_char_ptr_t foo (void);

/** \brief
 *  Frees a single Rust-allocated C string.
 */
void free_rust_string (
    char * string);

/** \brief
 *  Frees the input `Vec`,
 *  which recursively calls `free_rust_string` on its first `.len` elements.
 */
void free_rust_vec (
    Vec_char_ptr_t vec);
1 Like

Thank you for the code snippet, this is very close to I am looking for.
A small detail is that the pointer must be passed by reference rather than as a result.

Unfortunately I am unable to make argument point to the newly allocated memory.

cannot assign to `values`, as it is not declared as mutable

The argument is declared as mutable, so it seems there is a semantic difference between raw pointers in rust and C pointers. How would i change the address of double pointer values in rust?

#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn fmi2GetString(
    c: *const i32,
    vr: *const c_uint,
    nvr: usize,
    values: *mut *mut c_char,
) -> c_int {
    let get_string = || -> Result<i32, Error> {
        let references = unsafe { std::slice::from_raw_parts(vr, nvr as usize) }.to_vec();
        let h = unsafe { *c };

        let gil = Python::acquire_gil();
        let py = gil.python();

        // TODO replace with "map_error"
        let (values_vec, status): (Vec<String>, i32) = SLAVE_MANAGER
            .call_method1(py, "get_xxx", (h, references))
            .map_pyerr(py)?
            .extract(py)
            .map_pyerr(py)?;

        values = unsafe { vector_to_string_array(values_vec) }; // returns *mut *mut c_char

        Fmi2Status::try_from(status)?;

        Ok(status)
    };

    match get_string() {
        Ok(s) => s,
        Err(e) => {
            println!("{}", e);
            Fmi2Status::Fmi2Error.into()
        }
    }
}

If you want to write to the pointee, you need to use the dereference operator.

unsafe {
    *values = vector_to_string_array(values_vec);
}

It may not be clear from the code itself, values pointer can be null.
Would using the dereferencing operator and assigning to this not cause a segfault?

Never dereference a pointer like that. Dereferencing will try to read the previous value and drop it. If it is a pointer that might be not to critical (pointers don't implement Drop), but in other cases it might be problematic (e.g. Bug #1: Destructors running after assignment (double free))

Rust is correct here: cannot assign to values, as it is not declared as mutable

values is defined as values: *mut *mut c_char. That is: mutable pointer to mutable pointer to a c_char. values itself (the binding) is immutable, which is why you can't assign to it.

What you will need is values: *mut *mut *mut c_char, so that you then can write into that to point to your vector.

ptr::write(results, out.as_mut_ptr())

On the C side it's called as:

char **values;
int len = fmi2GetString(c, vr, nvr, &values);

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.