将一个数组指针分配给 *mut c_void

5
我正在为一个库编写绑定,其中有一个带有类型void*或在Rust中的*mut c_void参数的函数。我必须将一个数组分配给此参数,如何在Rust中实现?
我尝试了强制类型转换和使用transmute,但都不起作用(transmutec_void[u8]的大小不同)。如果有关系的话,我是从一个向量中获取片段的。
更新:也许正确的做法是使用vec.as_mut_ptr()
PLAYPEN: http://is.gd/KjgduZ

这是另一种方式,但解释是相同的:https://dev59.com/Uozda4cB1Zd3GeqPprL3#31238747 - oli_obk
@ker 你能写一个回答吗? - Zihemu
我不知道。我只会毫无改动地抄袭@DK的答案1:1。 - oli_obk
@ker 这种情况有点不同。我无法使用那个答案让某些东西正常工作。 - Zihemu
1
请展示你尝试过的代码。最好在playpen(http://play.rust-lang.org)中提供一个示例。然后我会解释如何使你的代码工作以及为什么需要进行一些更改。我知道它不会编译,但只需添加你想要的ffi和你尝试调用它的代码即可。 - oli_obk
1
C代码如何知道数组的长度?或者是硬编码的吗? - oli_obk
1个回答

8
你所描述的API看起来非常可疑。请记住,C中实际上没有“数组” - 数组只是指向同一类型多个值的开头的指针,这些值在内存中连续布置。因此,在C中不可能只是“分配”一个数组。有两个概念可以理解为“分配”到一个数组中:首先,将指向某个位置的指针分配给数组的开头:
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"

其次,它是将某些数据从一个指针复制到另一个指针,通常使用memcpy()或类似的函数来完成:

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`

你现在可能已经明白我所说的你的API为何是可疑的了。你的回调函数会被给予一个void *,但是并没有指示应该使用哪种“数组复制”方法。
如果是第一种情况,即将指针复制到数组的开头,那么void *类型就极其无用了。它并没有说明这个指针应该如何表示。看起来你正在尝试做这件事,然而,它不会像你想象的那样工作。下面是你代码的编译变体(请注意,它是错误的,很可能会导致你的程序崩溃;请参见下文):
#![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;
    }
}

(请注意,由于自动引用,您可以直接在包含向量的mut变量上调用as_mut_ptr())
参数类型现在不仅是*mut c_void,而是*mut *mut c_void,也就是指向*mut c_void的指针。这样调用此函数的程序可以将类型为void *的局部变量的指针传递给此函数,并获得指向实际数组的指针,类似于
void *data;
some_struct.callback_fn(&data);  // pointer to `demo` is stored in `some_struct`
// data is now whatever your `demo` function has assigned

请注意,您不能简单地让demo仅接受*mut c_void,因为您能做的唯一事情就是重新分配参数本身,但重新分配参数只会重新分配该参数值,即此参数所代表的局部变量。这在函数外部无法观察到。换句话说,以下代码(也是您提供的代码的变体):
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;
}

这段代码什么也不做,Rust很高兴指出这一点:

<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;
             ^~~~

我说*mut *mut c_void的代码是错误的原因是它实际上违反了内存安全性。如果你创建一个Vec实例并将其存储到本地变量中,当此变量超出作用域时,向量本身将被销毁,并释放其包装的内存。因此,使用as_ptr()as_mut_ptr()从中获取的每个指针都将变为无效。
有几种方法可以解决这个问题,最简单的方法是只需forget()向量:
use std::mem;

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

这种方法会“遗忘”向量-其析构函数不会被调用。然而,这样会在程序中引入内存泄漏。每次调用demo()都会分配一些内存,但不会释放,因此最终您的程序将使用所有可用内存,并可能在之后崩溃。在某些情况下,这是明智的做法,特别是在低级代码中。例如,您的API可能指定它只会调用此函数一次。
这种方法的另一个问题是与上述问题的逻辑结果有关。您的API可以指定应该由谁释放提供给它的指针的内存。例如,它可能要求传递使用malloc()分配的内存,因此它将自己使用free()来释放它。或者它可能指定您应定义另一个函数,在释放所有分配的内存时将调用该函数。无论哪种方式实现起来都有点不方便,我不会详细介绍如何实现,除非确实是您的情况。无论如何,您的API必须清楚地指定内存的所有者,并且您应该考虑这一点,因为Rust对所有权更加明确。
另一个可能性是,您的API要求您将某些数据复制到由void *指针指定的内存中。换句话说,它的实现包含像这样的代码:
char buffer[256];
some_struct.callback_fn(buffer);

该API期望在callback_fn调用后,buffer中填充了数据。

如果是这种情况,API必须自然地指定您的程序可以使用的缓冲区中的最大字节数,您的demo函数可能如下所示:

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());
    }
}

如果需要,你还可以使用std::slice::from_raw_parts_mut()data转换为&mut [u8],并使用clone_from_slice()方法或bytes::copy_memory()函数,但它们都不稳定,所以不能在稳定版本的Rust中使用。

在这种情况下,您应该特别注意不要溢出由调用程序提供给您的缓冲区。其最大大小应在API中指定。

另一个问题是仅对于字节数组(C端的char *,Rust端的&[u8]/&mut [u8])才能简单地复制数组。当您开始使用更大的类型时,例如i32,您将面临可移植性问题的可能性。例如,在C中,int没有定义大小,因此您无法盲目将&[i32]转换为具有四倍原始大小的&[u8],并从中将字节复制到*mut u8。这些问题应该非常谨慎地处理。


哇,现在这个答案肯定会帮助像我这样需要帮助的人。确实,C代码给了我最大长度,我会尝试你最后的代码示例,谢谢! - Zihemu
你真是救命恩人,非常感谢。依我之见,这应该被添加到书中的某个地方,FFI章节仍然非常模糊。再次感谢 :) - Zihemu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接