关于ffi:C库,释放了来自Rust的指针

C library freeing a pointer coming from Rust

我想对需要回调的C库进行Rust绑定,并且此回调必须返回指向C库的C样式char*指针,然后将其释放。
从某种意义上讲,回调必须暴露给我的库用户(可能使用闭包),并且我想提供一个尽可能方便的Rust接口(意味着如果可能接受String输出)。

但是,C库在尝试free()来自Rust分配的内存的指针时抱怨,这可能是因为Rust使用jemalloc且C库使用malloc。

所以目前我可以看到使用libc::malloc()的两种解决方法,但是它们都有缺点:

  • 给图书馆用户一个他必须填写的切片(不方便,并施加了长度限制)
  • 取得他的String输出,将其复制到malloc分配的数组中,然后释放String(无用的复制和分配)

有人能找到更好的解决方案吗?

这里等效于C库的接口,并且是理想情况的实现(如果C库可以释放在Rust中分配的String)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
extern crate libc;
use std::ffi::CString;
use libc::*;
use std::mem;

extern"C" {
    // The second parameter of this function gets passed as an argument of my callback
    fn need_callback(callback: extern fn(arbitrary_data: *mut c_void) -> *mut c_char,
                     arbitrary_data: *mut c_void);
}

// This function must return a C-style char[] that will be freed by the C library
extern fn my_callback(arbitrary_data: *mut c_void) -> *mut c_char {
    unsafe {
        let mut user_callback: *mut &'static mut FnMut() -> String = mem::transmute(arbitrary_data); //'
        let user_string = (*user_callback)();
        let c_string = CString::new(user_string).unwrap();
        let ret: *mut c_char = mem::transmute(c_string.as_ptr());
        mem::forget(c_string); // To prevent deallocation by Rust
        ret
    }
}

pub fn call_callback(mut user_callback: &mut FnMut() -> String) {
    unsafe {
        need_callback(my_callback, mem::transmute(&mut user_callback));
    }
}

C部分等效于此:

1
2
3
4
5
6
#include <stdlib.h>
typedef char* (*callback)(void *arbitrary_data);
void need_callback(callback cb, void *arbitrary_data) {
    char *user_return = cb(arbitrary_data);
    free(user_return); // Complains as the pointer has been allocated with jemalloc
}

这可能需要您进行一些烦人的工作,但是如何公开实现Write但由malloc分配的内存作为后盾的类型呢?然后,您的客户端可以使用write!宏(和朋友),而不是分配String

这是当前与Vec一起使用的方式:

1
2
let mut v = Vec::new();
write!(&mut v,"hello, world");

您"只是"需要实现这两种方法,然后您将拥有一个类似于流的接口。