【问题标题】:C library freeing a pointer coming from RustC 库释放来自 Rust 的指针
【发布时间】:2015-05-06 03:05:33
【问题描述】:

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

但是,当尝试 free() 来自 Rust 分配的内存的指针时,C 库会报错,可能是因为 Rust 使用 jemalloc 而 C 库使用 malloc。

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

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

谁能找到更好的解决方案?

这里是 C 库的接口等价物,以及理想情况的实现(如果 C 库可以释放在 Rust 中分配的字符串)

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 部分相当于这个:

#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
}

【问题讨论】:

    标签: rust ffi


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 哦,我没想到,谢谢!虽然如果我是正确的 malloc 有一个非常愚蠢的 realloc 功能,它只是做 malloc / copy / free 而 jemalloc 可以重新分配到位。因此,在 malloc 中实现的可增长类型可能意味着在重新分配期间有多个副本,这意味着使用经典字符串可能更方便、更快捷,然后执行单个 malloc / 副本。我想我需要做一些测试!
    • 或者你可以稍后使用rope和malloc。 Crates.io 有几条绳索。
    • @ArtemGr 绳子不会涉及到 Rust 端的分配,因此需要稍后通过 malloc 进行第二次分配?
    • @Vaelden 我不认为realloc 有一个“愚蠢”的实现,而且可能很难在全球范围内描述realloc - 如果每个平台都有不同的实现,我不会感到惊讶,而且您总是可以做一些链接器技巧来在运行时用自定义库替换malloc 和朋友。我同意如果您担心性能,分析是正确的解决方案!
    • @Shepmaster 当然。不过,这是构建字符串的最有效方式,因为它避免了在重新分配期间的复制。这里有一个奇怪的评论 - github.com/servo/servo/wiki/Workweek-encoding - "kmc: SM 使用绳索吗?zwarich: 每个人都使用。" ; ) 更不用说这种方式更节省内存,避免在缓冲区末尾长时间使用额外内存。
    猜你喜欢
    • 2021-08-19
    • 1970-01-01
    • 2013-12-30
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    相关资源
    最近更新 更多