将一个C数组传递给Rust函数

9

我正在尝试制作一个Rust动态链接库,并从其他语言(如C、Python等)中使用它。我已经成功地从Python调用了一个接受i32参数的Rust函数。现在,我正在尝试创建一个接受数组(或指向它的指针,或任何必要的内容以将数据集传递给Rust)的函数。

#![crate_type = "dylib"]
#[no_mangle]
pub extern "C" fn rust_multiply(size: i32, arrayPointer: &i32) -> i32 {
    *(arrayPointer)
}

这个工作效果很好。但是

#![crate_type = "dylib"]
#[no_mangle]
pub extern "C" fn rust_multiply(size: i32, arrayPointer: &i32) -> i32 {
    *(arrayPointer + 1) // trying to get next element
}

失败,显示错误信息为

error[E0614]: type `i32` cannot be dereferenced
 --> src/lib.rs:4:5
  |
4 |     *(arrayPointer + 1) // trying to get next element
  |     ^^^^^^^^^^^^^^^^^^^

做这个操作:

pub extern fn rust_multiply(size: i32, array: &[i32]) -> i32

如果像这样执行array[0]的操作,会出现“length = 0”的错误。


1
你可能不想要有接受 i32 或引用(例如 &i32)的 extern 函数。最好使用 C 类型,以保证与您的平台匹配 - 如答案中所示的 libc::uint32_t。此外,Rust 引用保证是非空的,但在通过 FFI 调用时没有强制执行。更安全的做法是接受一个 *const libc::uint32_t(再次如答案所示),然后在将其转换为引用之前断言它不为空。 - Shepmaster
2个回答

12

你需要花一些功夫提供一个纯粹的C API,并使用不安全代码来实现一些转换。幸运的是,这并不是那么困难:

extern crate libc;

#[no_mangle]
pub extern "C" fn rust_multiply(
    size: libc::size_t,
    array_pointer: *const libc::uint32_t,
) -> libc::uint32_t {
    internal_rust_multiply(unsafe {
        std::slice::from_raw_parts(array_pointer as *const i32, size as usize)
    }) as libc::uint32_t
}

fn internal_rust_multiply(array: &[i32]) -> i32 {
    assert!(!array.is_empty());
    array[0]
}

官网有一篇很好的 Rust FFI 介绍


4
请注意,当使用不安全块时,需要由程序员自行验证其中的代码是否符合 Rust 的安全要求。在这个例子中,引用永远不能为 NULL,因此你应该确保 array_pointer 不是 NULL(除非你可以保证它永远不会是 NULL,但这更加困难)。 - Shepmaster
@Shepmaster,这是领域内的问题(C FFI)。请注意,&指针也必须是非空的,因此,如果它可能为空,则OP代码中的签名也是错误的。 - user395760
2
@delnan,我可能误解了您的意思。我的观点是 *const libc::uint32_t 可以为NULL,因为这是完全有效的C代码,就如你所提到的一样。这段代码将其转换为切片,而不允许数据指针为空(不确定长度是否有影响——当数据为空时长度为0是否有关系?)。我只是说在调用 from_raw_parts 之前应该有一个 assert!(array_pointer != std::ptr::null())。这一切都是为了尽可能地保护我们自己免受疯狂的C语言调用者的侵害。 - Shepmaster
@Shepmaster 确定,我同意。我试图补充说明(1)问题中已经存在NULL问题,以及(2)在Rust与C交互时有些危险是无法排除的。 我必须承认后来想一想第二个观点有点离题。 - user395760
@delnan 绝对没错,说得好。我在评论中回应了(1)。不幸的是,我们对于(2)无能为力。C FFI 消费者总是可以传入 (-1, 0xDEADBEEF)。^_^ - Shepmaster

0

@swizard的回答获取无符号整数并将其转换为有符号整数。你可能想要的代码是

extern crate libc;

#[no_mangle]

pub extern "C" fn rust_multiply(
    size: libc::size_t,
    array_pointer: *const libc::int32_t,
) -> libc::int32_t {
    internal_rust_multiply(unsafe {
        std::slice::from_raw_parts(array_pointer as *const i32, size as usize)
    }) as libc::int32_t
}

fn internal_rust_multiply(array: &[i32]) -> i32 {
    assert!(!array.is_empty());
    array[0]
}

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