Return **char from `extern "C"` fn

I'm working on creating a C native interface to one of my Rust libraries. I used this blog post to work through exporting plain strings, and that works great. Unfortunately, this breaks down when I attempt returning a Vec<String>. The article has an example of doing this, but the author constructs strings directly (I.e. "Hello\0") and I'm trying to work through an actual Vec<String>, converting each to a *const c_char and returning the final pointer.

Here is my code. layers(key) is a function returning a Vec<String>.

#[no_mangle]
pub extern "C" fn perspective_get_layers(
    ptr: *mut FFIPerspective,
    key: *const c_char,
) -> *const *const c_char {
    if key.is_null() {
        return ptr::null();
    }
    let perspective = unsafe {
        ptr.as_mut()
    };
    if perspective.is_none() {
        return ptr::null();
    }
    let perspective = perspective.unwrap();
    let key = unsafe { CStr::from_ptr(key) };
    let key = key.to_string_lossy().into_owned();
    let layers = &perspective.0.layers(key);
    if layers.is_err() {
        return ptr::null();
    }
    let layers = layers.as_ref().unwrap();
    let layers = layers.iter().map(|v| {
        let v = CString::new(v.as_bytes()).unwrap();
        let ptr = v.as_ptr();
        forget(v);
        ptr
    }).collect::<Vec<*const c_char>>();
    let ptr = layers.as_ptr();
    forget(layers);
    ptr
}

Thanks.

I can't immediately tell what the error is, would you mind pointing it out?

This part is fine.
But if you return "char **" in terms of C, then I suppose you should add NULL at the end of array,
in other way how C part of your code find out length of array?

And also you have memory leak, because of to free Vec memory you need to know capacity.

You can look how in one of my projects I return Vec to C here: https://github.com/Dushistov/rust_swig/blob/15ab3bd3b88ef17c450ac3acc9a8ca482d4df534/macroslib/src/cpp/cpp-include.rs#L810

Yeah, sorry, don't have the error in front of me, but it was a segfault
in str_len (or some libc symbol version.) I can get it later, but I was
hoping there was some obvious issue with how I was returning the array,
which it seems like there might be.

So I guess I have to pack the array into some sort of #[repr(C)]
struct encoding its size? I'm trying to interface with JNA, and JNA does
support **char as a Java String[]. I guess I just need to figure out
how to pack this Vec<String> correctly.

@Dushistov, I'm a bit confused by some of your points. Do I need to
append a null pointer to the array to indicate where it ends? I looked
at your code, but IIRC SWIG kind of does its own thing with regard to
bindings, so I don't know how much of it is SWIG-specific, or what the
simplest way of getting a **char is.

Thanks for all the help.

OK, figured it out thanks to this Stack Overflow
answer
:


#[no_mangle]
pub extern "C" fn perspective_get_layers(
     ptr: *mut FFIPerspective,
     key: *const c_char,
) -> *const *const c_char {
     if key.is_null() {
         return ptr::null();
     }
     let perspective = unsafe {
         ptr.as_mut()
     };
     if perspective.is_none() {
         return ptr::null();
     }
     let perspective = perspective.unwrap();
     let key = unsafe { CStr::from_ptr(key) };
     let key = key.to_string_lossy().into_owned();
     let layers = &perspective.0.layers(key);
     if layers.is_err() {
         return ptr::null();
     }
     let layers = layers.as_ref().unwrap();
     let layers: Vec<_> = layers.iter()
         .map(|v| CString::new(v.as_str()).unwrap())
         .collect();
     let mut ptrs: Vec<_> = layers
         .iter()
         .map(|v| v.as_ptr())
         .collect();
     ptrs.push(ptr::null());
     let ptr: *const *const c_char = ptrs.as_ptr();
     forget(layers);
     forget(ptrs);
     ptr
}

With that code, my JNA returns a String[] and seems to work correctly.
Hopefully that doesn't leak--I'm assuming JNA cleans up after itself but
will have to research further.

Thanks, all, for the help.

That's impossible. I don't know what the char** -> String[] conversion does, but JNA can not possibly know what method your code used to allocate the memory, and thus cannot know how to free it.

Whatever the correct solution is, you will almost certainly need to define your own Free function that reconstructs the Vec and the Strings and drops them.


Update: From http://java-native-access.github.io/jna/5.1.0/javadoc/overview-summary.html:

Returned char* values are automatically copied into a String if the method signature returns String ( strdup , for example).

If the native method returns char* and actually allocates memory, a return type of Pointer should be used to avoid leaking the memory. It is then up to you to take the necessary steps to free the allocated memory.

Their example of strdup confuses me, because it seems to very clearly fall under the umbrella of the second paragraph I quoted.


By the way, you should prefer into_raw over as_ptr/forget. It's much easier to get right.

1 Like

By the way, if all what you need is cooperation with Java,
then I can not avoid for self-promotion,
with rust_swig you can write on Rust:

//suppose you have
struct Perspective { ... };
impl Perspective {
  fn get_alyers(&self, key: &str) -> Vec<String> { ... }
}

// you can write

foreign_class!(class Perspective {
   self_type Perspective;
   //some constructor
   fn Perspective::get_layers(&self, key: &str) -> Vec<String>;
});

And that's all, after processing foreign_class! macro rust_swig generates Java class Perspective with proper method get_layers, and take care about low-level stuff, like creation of CString, mem::forget and so on thing.

Hmm, can it export C-native interfaces as well? I have a hand-written
JNI interface, but it's kind of a pain to update my main code, update
the JNI interface, update my Kotlin wrappers, then write my Dart
interface to the Kotlin layer. I'm writing this native interface because
I wanted to use the same native interface for JNA, possible iOS,
possible Node, or wherever I eventually choose to run this code. Maybe
SWIG would be a better choice after all, so I'll give it a look.

So to confirm what I'm reading, every value I return from Rust needs to
be freed in Rust? I can't, say, rely on JNA's Memory classes that
automatically free() something created by malloc() because these
pointers weren't malloc'd?

Is that true of every value created by as_ptr/into_raw? Is it
possible to return a string that hasn't been allocated so at least I
wouldn't have to free strings? Haven't done C in a couple decades so
it's now rusty in more ways than one. :slight_smile:

Thanks.

Yes, it can generate code for usage Rust from C++, this is done by header only C++ classes,
that uses C API.
That's why I give you link in some comment above for how I return Vec via ffi,
this cod related to C++ Rust binding, not Java one.

That why I create rust_swig, it generate Rust/FFI and Java code, so I can use Rust on Android without pain.

Have no idea why you need Dart though.

Not get it, how JNA is related to iOS.
But in theory you can use rust_swig to generate C++ wrapper, then via Objective-C++ use
it on iOS, but I don't try it, because of I use rust_swig only with Java/Android and C++/Qt.

For creating robust code yes. Rust code can use different allocator in compare with JNA.
It may be malloc/free based or not, and even if it is malloc/free it can be from different C runtime that uses JNA, plus it may be some tricks, for example some smart pointer may allocate addition memory in addition to required for own purposes, and if you give pointer to free it crashes, because of real allocation address is raw_ptr - X.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.