Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign an array to *mut c_void

Tags:

c

rust

ffi

I am writing bindings for a library, where I have a function with a parameter of type void* aka *mut c_void in Rust. I have to assign an array to this parameter, how can I do this in Rust?

I've tried casting, transmute, it doesn't work (transmute says that c_void and [u8] are of different sizes). If it matters, I am getting the slice from a vector.

UPDATE: Perhaps it would be correct to somehow use vec.as_mut_ptr() instead?

PLAYPEN: http://is.gd/KjgduZ

like image 713
Zihemu Avatar asked Aug 01 '15 07:08

Zihemu


Video Answer


1 Answers

The API you have described looks very suspicious. Remember that there are actually no "arrays" in C - arrays are just another name for pointers to the beginning of multiple values of the same type which are laid continuously in the memory. Therefore, it is impossible to just "assign" an array in C. There are two concepts which may be understood as "assigning" to an array: first, assigning a pointer to the beginning of an array somewhere:

const char *s1 = "hello";
const char *s2 = "world";

const char *s = s1;  // make `s` contain a pointer to "hello"
s = s2;  // make `s` contain a pointer to "world"

Second, it is copying certain pieces of data from one pointer to another, which is usually done with memcpy() or something like it:

const char *s1 = "hello";

char s2[5];
memcpy(s2, s1, 5);  // copy 5 bytes from the memory pointed at by `s1` to the memory pointed at by `s2`

You can probably see now what I mean when I'm saying that your API is suspicious. Your callback function is given a void *, however, there is no indication which "array copy" method should be used.

If it is the first one, i.e. copying the pointer to the beginning of an array, then void * type is extremely unhelpful. It does not say how this pointer should be represented. It looks like that you're trying to do exactly this; however, it won't work as you probably think. Here is a compiling variant of your code (note that it is wrong and will most likely crash your program; see below):

#![feature(libc)]
extern crate libc;

use libc::c_void;

pub extern fn demo(data: *mut *mut c_void) {
    let mut vec = vec!(1, 2, 3);
    unsafe {
        *data = vec.as_mut_ptr() as *mut c_void;
    }
}

(Note that you can call as_mut_ptr() on a mut variable containing a vector directly due to autoderef)

The parameter type is now not just *mut c_void but *mut *mut c_void, that is, it is a pointer to *mut c_void. This way the program which calls this function could pass a pointer to a local variable of type void * to this function and get a pointer to an actual array, something like

void *data;
some_struct.callback_fn(&data);  // pointer to `demo` is stored in `some_struct`
// data is now whatever your `demo` function has assigned

Note that you just can't sensibly make demo accept just *mut c_void because the only thing you can do with it is to reassign the parameter itself, but reassigning the parameter will reassign only this parameter value, i.e. the local variable this parameter represents. This can't be observed outside of the function. In other words, the following code (which is also a variant of the one you provided):

pub extern fn demo(mut data: *mut c_void) {
    let mut vec = vec!(1, 2, 3);
    data = vec.as_mut_ptr() as *mut c_void;
}

does nothing, and Rust is glad to point this out:

<anon>:6:20: 6:28 warning: variable `data` is assigned to, but never used, #[warn(unused_variables)] on by default
<anon>:6 pub extern fn demo(mut data: *mut c_void) {
                            ^~~~~~~~
<anon>:8:5: 8:9 warning: value assigned to `data` is never read, #[warn(unused_assignments)] on by default
<anon>:8     data = vec.as_mut_ptr() as *mut c_void;
             ^~~~

The reason I said that the code with *mut *mut c_void is wrong is that it actually violates memory safety. If you create a Vec instance and store it to a local variable, when this variable goes out of scope, the vector itself will be destroyed and the memory it wraps will be freed. Therefore, every pointer obtained from it using as_ptr() or as_mut_ptr() will become invalid.

There are several ways to work this around, the simplest one is to just forget() the vector:

use std::mem;

let mut vec = vec![1, 2, 3];
*data = vec.as_mut_ptr() as *mut c_void;
mem::forget(vec);

This way the vector is "forgotten" - its destructor won't be called. This way, however, a memory leak is introduced to your program. With each call of demo() a bit more memory will be allocated but not freed, so eventually your program will use all of the available memory and probably crash afterwards. It is a sensible things to do in some context, however, especially in low-level code. For example, your API may specify that it will only call this function once.

Another problem with this approach is a logical consequence of the above one. Your API may specify who should free the memory at the pointer provided to it. For example, it may require passing a memory allocated with malloc() so it then will free it with free() by itself. Or it may specify that you should define another function which will be called when all allocated memory should be freed. Either way is somewhat inconvenient to implement in Rust; I won't go in details on how to do it unless this is indeed your case. Anyway, your API must clearly specify the owner of the memory, and you should take it into account because Rust is much more explicit about ownership.

Another possibility is that your API requires you to copy some data to the memory specified by void * pointer. In other words, its implementation contains a code like this one:

char buffer[256];
some_struct.callback_fn(buffer);

and it expects that after callback_fn invocation the buffer is filled with data.

If this is the case, the API must, naturally, specify the maximum number of bytes in the buffer that your program may use, and your demo function may look like this:

use std::ptr;
use libc::c_void;

pub extern fn demo(data: *mut c_void) {
    let vec: Vec<u8> = vec!(1, 2, 3);
    unsafe { 
        ptr::copy_nonoverlapping(vec.as_ptr(), data as *mut u8, vec.len());
    }
}

(alternatively, you can convert data to &mut [u8] with std::slice::from_raw_parts_mut() and use either clone_from_slice() method or bytes::copy_memory() function, but they both are unstable, so they can't be used on stable Rust)

In this case you should be especially careful not to overflow the buffer provided by the calling program to you. Its maximum size should be specified in the API.

Another concern is that copying an array is simple only for byte arrays (char * on C side, &[u8]/&mut [u8] on Rust side). When you start using larger types, like i32, you will get a possibility for portability problems. For example, in C int does not have a defined size, so you just can't blindly convert &[i32] to &[u8] with four times the original size and copy bytes from it to *mut u8. These problems should be taken care of very carefully.

like image 174
Vladimir Matveev Avatar answered Oct 14 '22 03:10

Vladimir Matveev