Is there a way to export a Vec<String>
to C (as *const *const c_char
for example) with Rust ffi? I could not find any answer in the internet.
thx,
Ronny
Is there a way to export a Vec<String>
to C (as *const *const c_char
for example) with Rust ffi? I could not find any answer in the internet.
thx,
Ronny
Not directly, because String
is not exactly a C style string, because it lacks trailing zero. So you need to convert your Vec<String>
into Vec<CString>
first, that should be converted to Vec<*const c_char>
, and that can be converted to *const *const c_char
.
I already tried this, but it seems not working:
0 #[repr(C)]
1 pub struct Array {
2 len: size_t,
3 data: *const *const c_char,
4 }
5
6 impl Array {
7 fn from_vec(mut vec: Vec<std::string::String>) -> Array {
8
9 let mut cstr_vec: Vec<CString> = vec![];
10 for s in vec {
11 let cstr = CString::new(s.as_str()).unwrap();
12 cstr_vec.push(cstr);
13 }
14 cstr_vec.shrink_to_fit();
15
16 let mut c_char_vec: Vec<*const c_char> = vec![];
17 for s in cstr_vec {
18 c_char_vec.push(s.as_ptr());
19 }
20
21 let array = Array {
22 data: c_char_vec.as_ptr() as *const *const c_char,
23 len: c_char_vec.len() as size_t,
24 };
25 mem::forget(c_char_vec);
26 array
27 }
28 }
When I try to read it, I get just messy (however, no seg fault) strings as result.
This will not work because c_char_vec
will be deallocated when you return from this function so whatever you pass down to the C function will point to deallocated memory (which in some instances may work depending on the deallocator but it's likely to break a lot)
cstr_vec
But that's why I used mem::forget(c_char_vec)
. Shouldn't this prevent de-allocation in Rust?
Ha! You are right! I needed to reference cstr_vec in the for loop to keep it alive. Then I mem::forget(cstr_vec)
and mem:forget(c_char_vec)
and it worked.
Thx!
Doh! Typo too early in the morning. Thanks
Not sure if you do that but allocating memory in Rust and then deallocating it in C is very risky and I wouldn't recommend it.
I agree. I would re-structure this code to keep the Rust data alive while you send it down to the C lib and allow the normal Rust cleanup to happen later when it's no longer needed.
Thank you for the warning.
I tried to approach this problem by giving ownership to the caller (which is actually Python).
The actual function which transports the Array
to C/Python is:
0 #[no_mangle]
1 pub extern "C" fn standard_finder_get_standards_list(ptr: *mut Standards,
2 array: *mut Array)
3 -> c_int {
4 let standards = unsafe {
5 assert!(!ptr.is_null());
6 &mut *ptr
7 };
8 unsafe {
9 *array = Array::from_vec(standards.standards
10 .iter()
11 .map(|ref c| c.chemsc.clone())
12 .collect()) as Array;
13 }
14
15 SF_SUCCESS
16 }
So far, it works. However, I am not quite sure if C/Python correctly frees the memory because of the two vectors cstr_vec
and c_char_vec
.
My guess is that if C/Python frees c_char_vec
it automatically frees cstr_vec
because this is were it points to.
The problem is that Rust can use a different allocator than the C does. I think you can enforce Rust to use the system allocator but I would say it's still a bit scary to use free
on pointers allocated by Rust.
The system allocator is not guaranteed to be malloc
/ free
. On Windows in particular it is HeapAlloc
which is not malloc
.
Maybe you should use an hour glass interface with C-safe types in the middle.
Thanks for the hints. The hour glass interface example is great. I'll try that.