Skip to content

Instantly share code, notes, and snippets.

@iskakaushik
Created February 12, 2021 05:31
Show Gist options
  • Save iskakaushik/1c5b8aa75c77479c33c4320913eebef6 to your computer and use it in GitHub Desktop.
Save iskakaushik/1c5b8aa75c77479c33c4320913eebef6 to your computer and use it in GitHub Desktop.
Transfer rust vec to c array strings.
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use std::{ptr, mem};
#[no_mangle]
unsafe extern "C" fn get_strings(outlen: *mut c_int) -> *mut *mut c_char {
let mut v = vec![];
// Let's fill a vector with null-terminated strings
v.push(CString::new("Hello").unwrap());
v.push(CString::new("World").unwrap());
v.push(CString::new("!").unwrap());
// Turning each null-terminated string into a pointer.
// `into_raw` takes ownershop, gives us the pointer and does NOT drop the data.
let mut out = v
.into_iter()
.map(|s| s.into_raw())
.collect::<Vec<_>>();
// Make sure we're not wasting space.
out.shrink_to_fit();
assert!(out.len() == out.capacity());
// Get the pointer to our vector.
let len = out.len();
let ptr = out.as_mut_ptr();
mem::forget(out);
// Let's write back the length the caller can expect
ptr::write(outlen, len as c_int);
// Finally return the data
ptr
}
#[no_mangle]
unsafe extern "C" fn free_string_array(ptr: *mut *mut c_char, len: c_int) {
let len = len as usize;
// Get back our vector.
// Previously we shrank to fit, so capacity == length.
let v = Vec::from_raw_parts(ptr, len, len);
// Now drop one string at a time.
for elem in v {
let s = CString::from_raw(elem);
mem::drop(s);
}
// Afterwards the vector will be dropped and thus freed.
}
/*
// The C code calling into our Rust part:
#include <stdio.h>
char** get_strings(int* outlen);
void free_string_array(char **ptr, int len);
int main(int argc, char** argv)
{
int len;
char** s = get_strings(&len);
for (int i=0; i<len; i++) {
printf("String %d: %s\n", i, s[i]);
}
free_string_array(s, len);
return 0;
}
*/
@bkstein
Copy link

bkstein commented Jun 2, 2022

Are you sure, that manually dropping the strings (line 48) is necessary? From the docs:
"(CString::from_raw()) Retakes ownership of a CString that was transferred to C via CString::into_raw."
This means, that the loop can be written as

    for elem in v {
        CString::from_raw(elem);
    }

Or do I miss something here?

@bkstein
Copy link

bkstein commented Jun 2, 2022

OK, looks like the returned value must be used. But this should work:

    for elem in v {
        let _ = CString::from_raw(elem);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment